2011-12-29 49 views
36

Tôi đang cố lưu trữ mật khẩu một cách an toàn trong cơ sở dữ liệu và tôi đã chọn lưu trữ hàm băm của nó được tạo bằng hàm PBKDF2. Tôi muốn làm điều này bằng cách sử dụng thư viện lâu đài bouncy nhưng tôi không biết tại sao tôi không thể làm cho nó hoạt động bằng cách sử dụng giao diện JCE ... Vấn đề là tạo băm trong 3 chế độ khác nhau:
1. sử dụng PBKDF2WithHmacSHA1 nhà máy khóa bí mật được cung cấp bởi ánh nắng mặt trời
2. sử dụng api lâu đài bouncy trực tiếp
3. sử dụng lâu đài bouncy qua JCE
kết quả trong 2 giá trị khác biệt: một chung cho hai đầu tiên và một cho các nhóm thứ ba.PBKDF2 với bouncycastle trong Java

Đây là mã của tôi:

//Mode 1 

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    KeySpec keyspec = new PBEKeySpec("password".toCharArray(), salt, 1000, 128); 
    Key key = factory.generateSecret(keyspec); 
    System.out.println(key.getClass().getName()); 
    System.out.println(Arrays.toString(key.getEncoded())); 

    //Mode 2 

    PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(); 
    generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(("password").toCharArray()), salt, 1000); 
    KeyParameter params = (KeyParameter)generator.generateDerivedParameters(128); 
    System.out.println(Arrays.toString(params.getKey())); 

    //Mode 3 

    SecretKeyFactory factorybc = SecretKeyFactory.getInstance("PBEWITHHMACSHA1", "BC"); 
    KeySpec keyspecbc = new PBEKeySpec("password".toCharArray(), salt, 1000, 128); 
    Key keybc = factorybc.generateSecret(keyspecbc); 
    System.out.println(keybc.getClass().getName()); 
    System.out.println(Arrays.toString(keybc.getEncoded())); 
    System.out.println(keybc.getAlgorithm()); 

Tôi biết rằng PBKDF2 được thực hiện sử dụng HMAC SHA1 cho nên đó là lý do tại sao tôi đã chọn như thuật toán trong phương pháp cuối cùng "PBEWITHHMACSHA1" mà tôi lấy từ các tài liệu java lâu đài bouncy .

Kết quả như sau:

com.sun.crypto.provider.SunJCE_ae 
[-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74] 
[-53, 29, 113, -110, -25, 76, 115, -127, -64, 74, -63, 102, 75, 81, -21, 74] 
org.bouncycastle.jce.provider.JCEPBEKey 
[14, -47, -87, -16, -117, -31, 91, -121, 90, -68, -82, -31, -27, 5, -93, -67, 30, -34, -64, -40] 
PBEwithHmacSHA 

Bất kỳ ý tưởng?

+0

Tôi không chắc chắn những gì "Chế độ 3" đang làm, nhưng tôi muốn bỏ qua nó. Đầu ra của nó là 160 bit, không phải 128, như bạn đã yêu cầu. 160 bit là kích thước của hàm băm SHA-1. Đối với tính di động, tôi muốn gắn với "Chế độ 1." – erickson

+0

Tôi đồng ý với erickson - có cần thiết thực sự để bạn sử dụng "Chế độ 3" hoặc "Chế độ 1" có thể được chấp nhận để lưu trữ mật khẩu an toàn không? Câu hỏi chi tiết tốt cho câu hỏi đầu tiên của bạn ở đây bằng cách này. –

+1

Không cần thực sự. Tôi chỉ cố gắng hiểu tại sao PBEWITHHMACSHA1 từ BouncyCastle không làm điều tương tự. Và tôi đồng ý, tôi sẽ không chọn phương pháp thứ hai vì các vấn đề về tính di động. –

Trả lời

28

Tóm lại, lý do cho sự khác biệt là thuật toán PBKDF2 ở chế độ # 1 và # 2 sử dụng PKCS # 5 v2 lược đồ 2 (PKCS5S2) để tạo khóa lặp, nhưng nhà cung cấp BouncyCastle cho "PBEWITHHMACSHA1" ở chế độ # 3 sử dụng thuật toán PKCS # 12 v1 (PKCS12) thay thế. Đây là các thuật toán tạo khóa hoàn toàn khác nhau, vì vậy bạn sẽ nhận được các kết quả khác nhau.

Thêm chi tiết về lý do tại sao điều này là như vậy và tại sao bạn nhận được các kết quả có kích thước khác nhau được giải thích bên dưới.

Trước tiên, khi bạn đang xây dựng một JCE KeySpec, tham số keyLength chỉ biểu thị "tùy chọn" cho nhà cung cấp về kích thước khóa bạn muốn. Từ the API docs:

Lưu ý: điều này được sử dụng để cho biết tùy chọn về độ dài khóa cho mật mã có khóa thay đổi. Kích thước khóa thực tế phụ thuộc vào việc thực hiện của từng nhà cung cấp.

Các nhà cung cấp Bouncy Castle dường như không tôn trọng tham số này, xét the source of JCEPBEKey, vì vậy bạn nên mong đợi để có được một chìa khóa 160-bit trở lại từ bất kỳ nhà cung cấp BC trong đó sử dụng SHA-1 khi sử dụng API JCE.

Bạn có thể xác nhận điều này bằng cách lập trình truy cập vào phương pháp getKeySize() trên trở keybc biến trong mã thử nghiệm của bạn:

Key keybc = factorybc.generateSecret(keyspecbc); 
// ... 
Method getKeySize = JCEPBEKey.class.getDeclaredMethod("getKeySize"); 
getKeySize.setAccessible(true); 
System.out.println(getKeySize.invoke(keybc)); // prints '160' 

Bây giờ, để hiểu những gì các "PBEWITHHMACSHA1" nhà cung cấp tương ứng, bạn có thể tìm thấy những điều sau đây the source for BouncyCastleProvider:

put("SecretKeyFactory.PBEWITHHMACSHA1", 
    "org.bouncycastle.jce.provider.JCESecretKeyFactory$PBEWithSHA"); 

Và việc thực hiện JCESecretKeyFactory.PBEWithSHA trông như thế này:

public static class PBEWithSHA 
    extends PBEKeyFactory 
{ 
    public PBEWithSHA() 
    { 
     super("PBEwithHmacSHA", null, false, PKCS12, SHA1, 160, 0); 
    } 
} 

Bạn có thể thấy ở trên rằng nhà máy chính này sử dụng thuật toán PKCS # 12 v1 (PKCS12) để tạo khóa lặp. Tuy nhiên, thuật toán PBKDF2 mà bạn muốn sử dụng cho băm mật khẩu sử dụng PKCS # 5 v2 scheme 2 (PKCS5S2) thay thế. Đây là lý do tại sao bạn nhận được kết quả khác nhau.

tôi đã có một cái nhìn nhanh chóng thông qua các nhà cung cấp JCE đăng ký tại BouncyCastleProvider, nhưng không thể nhìn thấy bất kỳ thuật toán thế hệ quan trọng mà sử dụng PKCS5S2 ở tất cả, hãy để một mình một trong đó cũng sử dụng nó với HMAC-SHA-1. Vì vậy, tôi đoán bạn đang mắc kẹt với việc sử dụng triển khai Mặt trời (chế độ # 1 ở trên) và mất tính di động trên các JVM khác hoặc sử dụng trực tiếp các lớp Bouncy Castle (chế độ # 2 ở trên) và yêu cầu thư viện BC trong thời gian chạy .

Dù bằng cách nào, bạn có lẽ nên chuyển sang khóa 160 bit, vì vậy bạn không cắt xén mã băm SHA-1 được tạo không cần thiết.

2

Tôi tìm thấy phương pháp Crypto-Only BC (thực sự từ gói cms của BC) hoạt động để tạo mã hóa mật khẩu dựa trên UTF-8. Bằng cách này tôi có thể tạo ra sản lượng KDF đó là tương thích với

http://packages.python.org/passlib/lib/passlib.hash.cta_pbkdf2_sha1.html#passlib.hash.cta_pbkdf2_sha1

private byte[] calculatePasswordDigest(char[] pass, byte[] salt, int iterations) 
    throws PasswordProtectionException 
{ 
    try 
    { 
     /* JCE Version (does not work as BC uses PKCS12 encoding) 
     SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWITHHMACSHA1","BC"); 
     PBEKeySpec ks = new PBEKeySpec(pass, salt, iterations,160); 
     SecretKey digest = kf.generateSecret(ks); 
     return digest.getEncoded(); 
     */ 
     PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(); 
     gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pass), salt, iterations); 
     byte[] derivedKey = ((KeyParameter)gen.generateDerivedParameters(160)).getKey(); 
     return derivedKey; 
    } 
    catch(Exception e) 
    { 
     LOG.error("Failed to strengthen the password with PBKDF2.",e); 
     throw new PasswordProtectionException(); 
    } 
}