I was looking at how the password was getting converted to bytes today. The PKCS12 spec (downloaded from http://www.rsa.com/rsalabs/node.asp?id=2138, page 14) says the password bytes are BMPStrings with a null terminator and no byte order marks. It shows a sample password of "Beavis" and the bytes (in hex) that should be produced from that password: 00 42 00 65 00 61 00 76 00 69 00 73 00 00
Both the JSS and BC BMPString classes produce correct output when given "Beavis\0" (ignoring they have the byte order marks at the front which could be removed fairly easily). The PasswordConverter class in PKCS12 package does the proper thing completely on its own if passed in "Beavis". It removes the BOM and adds the NULL at the end and everything is 2 bytes per char. The 4th is basically just doing the same thing that the code inside the PWConverter class (code below) does. JSS bmpstring password bytes : 1e0e0042006500610076006900730000 BC bmpstring password bytes : 1e0e0042006500610076006900730000 JSS pw converter password bytes : 0042006500610076006900730000 Java password bytes : 4265617669730000 custom password bytes : 4265617669730000 Jss password object charToByte : 42656176697300 What's weird is that the last line of output is the thing that will successfully decrypt the epki bytes passed back from NSS and that doesn't make sense at all because it's not 2 bytes per char, but it does have 2 bytes for the null at the end. (I found out why below...) - When getEncryptedPrivateKeyInfo is called you pass in a org.mozilla.jss.util.Password. This gets passed down to NSS which calls back to the java object and runs the non-public method getByteCopy() (http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs11/PK11Store.c#532). - getByteCopy calls charToByte which is hardcoded to use a null-terminated UTF8 (which only adds 1 of the null bytes so at this point we end up with 42656176697300 after inputting "Beavis" as the password) (http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/util/Password.java#216) - The native nss code then goes on to add the second 1 byte null at the end in a somewhat shady way (http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs11/PK11Store.c#534). It just adds one to the length. I'm not 100% on NSS coding conventions (or C for that matter), but I don't see anywhere in there where the memory for the SECItem holding the password is zeroed so is just adding 1 to the length ok? So if the Password object was created with the password "Beavis" the bytes that NSS would use for creating the epki would be 4265617669730000. I'm pretty sure this is where the incompatiblities are coming from. The only reason the PKCS12 is usable after JSS builds it is that JSS itself re-encrypts the key by calling SafeBag.createEncryptedPrivateKeyBag, you pass in a password object and when it creates the epki internally it passes in an instance of PasswordConverter from the pkcs12 package (http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs12/SafeBag.java#307) (which, as demonstrated above, produces bytes according to the PKCS12 spec) so the java/jss-built EPKI is encrypted according to spec). The same goes for AuthenticatedSafe.addEncryptedSafeContents, it also uses a "PasswordConverter" instance (http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs12/AuthenticatedSafes.java#354). This is why the shady home-grown KeyGenerator.CharToByteConverter is needed for decrypting the CryptoStore-provided epki(NSS at a lower level) which doesn't act according to the PKCS12 spec. This is why openssl and Java cannot decrypt these EPKI structures, they're deriving the wrong key given the same password text. So it seems the Password class needs a way to specify what "char to byte" conversion method should be used as null-terminated UTF8 is not the correct way to do it in all cases. That or maybe provide a way to pass the converted bytes themselves in to the NSS-native method Java_org_mozilla_jss_pkcs11_PK11Store_getEncryptedPrivateKeyInfo? (http://mxr.mozilla.org/security/source/security/jss/org/mozilla/jss/pkcs11/PK11Store.c#482). The code to produce the above samples (some BouncyCastle classes): Hex hex = new Hex(); char[] passChars = new char[]{'B','e','a','v','i','s','\0'}; String passString = "Beavis\0"; //JSS BMPString BMPString testString = new BMPString(passChars); byte[] passBytes = ASN1Util.encode(testString); System.out.println("JSS bmpstring password bytes : " + new String(hex.encode(passBytes))); //BC bmpstring org.bouncycastle.asn1.DERBMPString bcBMPString = new org.bouncycastle.asn1.DERBMPString(passString); byte[] bcPassBytes = bcBMPString.getDEREncoded(); System.out.println("BC bmpstring password bytes : " + new String(hex.encode(bcPassBytes))); //JSS pkcs12.PasswordConverter PasswordConverter pc = new PasswordConverter(); byte[] jssPWbytes = pc.convert("Beavis".toCharArray()); System.out.println("JSS pw converter password bytes : " + new String(hex.encode(jssPWbytes))); //these 2 match output byte[] javaPassBytes = (passString + '\0').getBytes("UTF-8"); System.out.println("Java password bytes : " + new String(hex.encode(javaPassBytes))); PWConverter converter = new PWConverter(); byte[] customPassBytes = converter.convert(passChars); System.out.println("custom password bytes : " + new String(hex.encode(customPassBytes))); byte[] jssPasswordBytes = password.charToByte("Beavis".toCharArray()); System.out.println("Jss password object charToByte : " + new String(hex.encode(jssPasswordBytes))); Our PWConverter: class PWConverter implements KeyGenerator.CharToByteConverter { public byte[] convert(char[] chars) { try { String s = new String(chars); byte[] raw = s.getBytes("UTF-8"); byte[] cooked = new byte[raw.length + 1]; System.arraycopy(raw, 0, cooked, 0, raw.length); return cooked; } catch (Exception e) { return null; } } } Dave _______________________________________________ dev-tech-crypto mailing list dev-tech-crypto@lists.mozilla.org https://lists.mozilla.org/listinfo/dev-tech-crypto