假设我试图从使用基本身份验证/基本证书的RESTful api中提取,那么在我的程序中存储用户名和密码的最佳方式是什么?现在它只是以明文形式存在。
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("myName@myserver","myPassword1234");
有没有更安全的方法?
谢啦
加密凭据通常不是好建议。加密的东西可以被解密。常见的最佳做法是将密码存储为加盐散列。哈希不能解密。添加盐是为了击败与彩虹表蛮力猜测。只要每个用户ID都有自己的随机盐,攻击者就必须为盐的每个可能值生成一组表,从而在宇宙的生命周期内快速使这种攻击不可能发生。这就是为什么如果你忘记了密码,网站通常不能给你发密码,但他们只能“重置”密码。他们没有存储你的密码,只有一个散列。
密码散列并不是很难自己实现,但它是一个很常见的问题,无数其他人已经为您解决了它。我发现jBcrypt很容易使用。
作为防止暴力猜测密码的额外保护,通常的最佳做法是在使用错误密码进行一定次数的登录尝试后,强制用户ID或远程IP等待几秒钟。如果没有这个,蛮力攻击者可以猜测服务器每秒可以处理的密码数量。能够每10秒猜测100个密码或一百万个密码之间有着巨大的差异。
我的印象是,您已经在源代码中包含了用户名/密码组合。这意味着,如果你想更改密码,你必须重新编译、停止并重新启动你的服务,这也意味着任何获得你的源代码的人都拥有你的密码。常见的最佳实践是永远不要这样做,而是将凭据(用户名、密码哈希、密码盐)存储在数据存储中
如果您使用的是基本身份验证,那么应该将其与SSL结合起来,以避免以base64编码的纯文本传递凭据。你不想让嗅探你信息包的人很容易得到你的证件。此外,不要在源代码中硬编码您的凭据。使其可配置。从配置文件中读取它们。在将凭据存储到配置文件之前,您应该对其进行加密,应用程序从配置文件中读取凭据后,应该对其进行解密。
如果您将身份验证系统作为一个整体进行设计,则不应存储密码,即使密码已加密。存储散列,并检查登录期间提供的密码是否与同一散列匹配。这样,数据库上的安全漏洞就可以避免暴露用户的密码。
话虽如此,对于要按原样存储数据(在本例中为密码)的情况,然后以从内到外的心态,以下是一些保护流程的步骤:
第一步,您应该将密码处理从字符串
更改为字符数组
。
这是因为字符串
是一个不可变的
对象,因此即使该对象被设置为null
,它的数据也不会立即被清除;数据被设置为垃圾收集,这会带来安全问题,因为恶意程序可能会在清理该字符串
(密码)数据之前访问该数据。
这就是为什么Swing的JPasswordField的getText()
方法被弃用,以及getPassword()
使用字符数组的主要原因。
第二步是加密您的凭据,只在身份验证过程中临时解密它们。或者在服务器端对其进行散列,存储该散列,然后“忘记”原始密码。
这与第一步类似,确保您的漏洞时间尽可能短。
建议您不要硬编码凭证,而是以集中、可配置且易于维护的方式存储凭证,例如配置或属性文件或数据库。
您应该在保存文件之前对凭据进行加密,此外,还可以对文件本身进行第二次加密(对凭据进行两层加密,对其他文件内容进行一层加密)。
请注意,上述两个加密过程中的每一个都可以是多层的。作为概念性示例,每个加密都可以是三重数据加密标准(又名TDES和3DES)的单独应用。
在你的当地环境得到适当保护后(但请记住,它永远不会“安全”!),第三步是使用TLS(传输层安全)或SSL(安全套接字层)对传输过程应用基本保护。
第四步是应用其他保护方法。
例如,对“使用”编译应用模糊技术,以避免(即使很快)暴露安全措施,以防您的程序被Eve女士、Mallory先生或其他人(坏人)获取并反编译。
更新1:
作者:达米恩。贝尔的要求,这里有一个涵盖第一步和第二步的例子:
//These will be used as the source of the configuration file's stored attributes.
private static final Map<String, String> COMMON_ATTRIBUTES = new HashMap<String, String>();
private static final Map<String, char[]> SECURE_ATTRIBUTES = new HashMap<String, char[]>();
//Ciphering (encryption and decryption) password/key.
private static final char[] PASSWORD = "Unauthorized_Personel_Is_Unauthorized".toCharArray();
//Cipher salt.
private static final byte[] SALT = {
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,};
//Desktop dir:
private static final File DESKTOP = new File(System.getProperty("user.home") + "/Desktop");
//File names:
private static final String NO_ENCRYPTION = "no_layers.txt";
private static final String SINGLE_LAYER = "single_layer.txt";
private static final String DOUBLE_LAYER = "double_layer.txt";
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws GeneralSecurityException, FileNotFoundException, IOException {
//Set common attributes.
COMMON_ATTRIBUTES.put("Gender", "Male");
COMMON_ATTRIBUTES.put("Age", "21");
COMMON_ATTRIBUTES.put("Name", "Hypot Hetical");
COMMON_ATTRIBUTES.put("Nickname", "HH");
/*
* Set secure attributes.
* NOTE: Ignore the use of Strings here, it's being used for convenience only.
* In real implementations, JPasswordField.getPassword() would send the arrays directly.
*/
SECURE_ATTRIBUTES.put("Username", "Hypothetical".toCharArray());
SECURE_ATTRIBUTES.put("Password", "LetMePass_Word".toCharArray());
/*
* For demosntration purposes, I make the three encryption layer-levels I mention.
* To leave no doubt the code works, I use real file IO.
*/
//File without encryption.
create_EncryptedFile(NO_ENCRYPTION, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 0);
//File with encryption to secure attributes only.
create_EncryptedFile(SINGLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 1);
//File completely encrypted, including re-encryption of secure attributes.
create_EncryptedFile(DOUBLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 2);
/*
* Show contents of all three encryption levels, from file.
*/
System.out.println("NO ENCRYPTION: \n" + readFile_NoDecryption(NO_ENCRYPTION) + "\n\n\n");
System.out.println("SINGLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(SINGLE_LAYER) + "\n\n\n");
System.out.println("DOUBLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(DOUBLE_LAYER) + "\n\n\n");
/*
* Decryption is demonstrated with the Double-Layer encryption file.
*/
//Descrypt first layer. (file content) (REMEMBER: Layers are in reverse order from writing).
String decryptedContent = readFile_ApplyDecryption(DOUBLE_LAYER);
System.out.println("READ: [first layer decrypted]\n" + decryptedContent + "\n\n\n");
//Decrypt second layer (secure data).
for (String line : decryptedContent.split("\n")) {
String[] pair = line.split(": ", 2);
if (pair[0].equalsIgnoreCase("Username") || pair[0].equalsIgnoreCase("Password")) {
System.out.println("Decrypted: " + pair[0] + ": " + decrypt(pair[1]));
}
}
}
private static String encrypt(byte[] property) throws GeneralSecurityException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
//Encrypt and save to temporary storage.
String encrypted = Base64.encodeBytes(pbeCipher.doFinal(property));
//Cleanup data-sources - Leave no traces behind.
for (int i = 0; i < property.length; i++) {
property[i] = 0;
}
property = null;
System.gc();
//Return encryption result.
return encrypted;
}
private static String encrypt(char[] property) throws GeneralSecurityException {
//Prepare and encrypt.
byte[] bytes = new byte[property.length];
for (int i = 0; i < property.length; i++) {
bytes[i] = (byte) property[i];
}
String encrypted = encrypt(bytes);
/*
* Cleanup property here. (child data-source 'bytes' is cleaned inside 'encrypt(byte[])').
* It's not being done because the sources are being used multiple times for the different layer samples.
*/
// for (int i = 0; i < property.length; i++) { //cleanup allocated data.
// property[i] = 0;
// }
// property = null; //de-allocate data (set for GC).
// System.gc(); //Attempt triggering garbage-collection.
return encrypted;
}
private static String encrypt(String property) throws GeneralSecurityException {
String encrypted = encrypt(property.getBytes());
/*
* Strings can't really have their allocated data cleaned before CG,
* that's why secure data should be handled with char[] or byte[].
* Still, don't forget to set for GC, even for data of sesser importancy;
* You are making everything safer still, and freeing up memory as bonus.
*/
property = null;
return encrypted;
}
private static String decrypt(String property) throws GeneralSecurityException, IOException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return new String(pbeCipher.doFinal(Base64.decode(property)));
}
private static void create_EncryptedFile(
String fileName,
Map<String, String> commonAttributes,
Map<String, char[]> secureAttributes,
int layers)
throws GeneralSecurityException, FileNotFoundException, IOException {
StringBuilder sb = new StringBuilder();
for (String k : commonAttributes.keySet()) {
sb.append(k).append(": ").append(commonAttributes.get(k)).append(System.lineSeparator());
}
//First encryption layer. Encrypts secure attribute values only.
for (String k : secureAttributes.keySet()) {
String encryptedValue;
if (layers >= 1) {
encryptedValue = encrypt(secureAttributes.get(k));
} else {
encryptedValue = new String(secureAttributes.get(k));
}
sb.append(k).append(": ").append(encryptedValue).append(System.lineSeparator());
}
//Prepare file and file-writing process.
File f = new File(DESKTOP, fileName);
if (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
} else if (f.exists()) {
f.delete();
}
BufferedWriter bw = new BufferedWriter(new FileWriter(f));
//Second encryption layer. Encrypts whole file content including previously encrypted stuff.
if (layers >= 2) {
bw.append(encrypt(sb.toString().trim()));
} else {
bw.append(sb.toString().trim());
}
bw.flush();
bw.close();
}
private static String readFile_NoDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
File f = new File(DESKTOP, fileName);
BufferedReader br = new BufferedReader(new FileReader(f));
StringBuilder sb = new StringBuilder();
while (br.ready()) {
sb.append(br.readLine()).append(System.lineSeparator());
}
return sb.toString();
}
private static String readFile_ApplyDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
File f = new File(DESKTOP, fileName);
BufferedReader br = new BufferedReader(new FileReader(f));
StringBuilder sb = new StringBuilder();
while (br.ready()) {
sb.append(br.readLine()).append(System.lineSeparator());
}
return decrypt(sb.toString());
}
一个完整的例子,解决每一个保护步骤,将远远超过我认为这个问题是合理的,因为它是关于“步骤是什么”,而不是“如何应用它们”。
这将大大超出我的答案(最后是抽样),而其他关于标准操作的问题已经指向这些步骤的“如何”,更合适,并对每个步骤的实施提供更好的解释和抽样。
问题内容: 假设我试图从使用基本身份验证/基本证书的RESTful API中提取信息,那么在该程序中存储该用户名和密码的最佳方法是什么?现在,它只是以纯文本格式坐在那里。 有什么方法可以做到更注重安全性吗? 问题答案: 在@ Damien.Bell的请求下,以下示例涵盖了第一步和第二步: 一个解决每个保护步骤的完整示例将远远超出我认为对该问题的合理范围,因为它是关于“步骤是什么”而不是“如何应用它
> 步骤: request=oauthClientRequest.AuthorizationProvider(OAuthProviderType.google)//AuthorizationProvider(OAuthProviderType.google).setState(OAuth.oauth_state).setResponseType(Oauth.oauth_code).setreDir
问题内容: System.setProperty(“http.proxySet”, “true”); System.setProperty("java.net.useSystemProxies”, “true”); System.setProperty(“http.proxyHost”, “192.168.1.103”); System.setProperty(“http.proxyPort”,
问题内容: 在哪里可以找到以纯JavaScript实现适合AJAX的HTTP基本身份验证客户端的参考代码? 可以独立于YUI之类的JS工具包使用代码的额外点或代码的指针。Java,Flash / Flex,PHP框架等都没有意义。 问题答案: 该方法的五参数版本允许您指定用户名和密码。(WHATWG规格)
我正试图让Eclipse Luna(在Debian 8上)通过需要身份验证的WiFi代理连接到互联网。我可以设置代理主机、端口、用户名和密码,然后应用设置并关闭对话框。下次我打开它时,它没有用户名和密码设置,尽管主机和端口被正确保留。根据需要将活动提供程序设置为手动。 出于某种原因,它似乎没有保存身份验证详细信息,我无法连接到互联网。 这是虫子还是我做错了什么? 此外,Eclipse将代理设置存储
我一直在尝试使用AWS Cognito实现无密码身份验证 我关注了这些文章:https://medium.com/digicred/password-less-authentication-in-cognito-cafa016d4db7 https://medium.com/@pjatocheseminario/passwordless-api-using-cognito-and-serverle