Difference in key lengths between crypto.pbkdf2 (node.js) and PBEKeySpec - java

I'm working on some interoperable code for encrypting/decrypting strings between Java and node.js and have managed to get node.js to decrypt what Java has encrypted with this being the final part to successful decryption: the secret key.
To derive a secret key in Java, we write:
private static Key deriveSecretKey(String secretKeyAlgorithm, String secretKey, String salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_ALGORITHM);
KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), char2byte(salt), 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), secretKeyAlgorithm);
return secret;
}
Notice the key length passed to PBEKeySpec() is 128 here. In node.js, however, I get an "Invalid key length" if I try to use 128 and actually have to use 16 here instead:
crypto.pbkdf2(key_value, salt_value, 65536, 16, function(err, key) {
var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
// decipher.setAutoPadding(false);
var decoded = decipher.update(ciphertext, 'binary', 'utf8');
decoded += decipher.final('utf8');
console.log('Result: ' + decoded);
});
Console output:
Result: Super secret stuff -- right here.
Curious as to why the difference when specifying key lengths between these two functions. Thanks!

Normally, key sizes are defined in bits. However, most cryptographic libraries don't handle bit sizes that cannot be divided by 8 particularly well - the output is almost always in octets (8-bit bytes). So it is up to the designer of the API if the user has to specify the size in bits, or in the number of octets in the octet string (byte array).
The only way to really know why bits or bytes are being chosen is to ask the person who designed the library. In my own code, I do try to keep to (ad-hoc) standards - so bits for key sizes. If it's unclear from the context which is which, it is probably best to use names such as blockSizeBits or blockSizeBytes. Documentation may be of help too of course, but using specific identifiers is best in my opinion.

Related

Default AES length for java crypto cipher

I have a query about the default AES key length and symmetric/asymmetric mode in javax.crypto.Cipher library.
I created a cipher as follows (I am in Java8 & Scala 2.12.x):
val mycipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
mycipher.init(Cipher.ENCRYPT_MODE, givemeKey(), givemeIV())
private def givemeKey(keyPass: Option[String] = None) = {
new SecretKeySpec(new BASE64Decoder().decodeBuffer('XxXxXxYyYyYXxZzZz-5z3y**\n'), "AES")
}
private def givemeIV() = {
val myuuid = UUID.randomUUID().toString.split("-").mkString("").getBytes()
val mybyteIV = new Array[Byte](16)
System.arraycopy(myuuid, 0, mybyteIV, 0, 16)
val returnIV = new IvParameterSpec(mybyteIV)
returnIV
}
I did look into documentations of Cipher package. Still I have two questions which I can not able to figure out, hence reaching out to you:
What is the length of AES here? AES-256 or something else?
What is the 'type' (symmetric or asymmetric). I think it's symmetric -- just want to confirm.
According to the javadocs for javax.crypto.Cipher, that string ("AES/CBC/PKCS5Padding") gives you an AES cipher algorithm.
There isn't a default key length. (But there is a guarantee that every standard Java SE platform supports AES keys of 128 bits. Some Java SE platforms support other AES key lengths (192 & 256) as well.)
The actual key length is not part of the algorithm name / Cipher object. It will depend on whatever key that givemeKey is returning. However in your example the base64 encoded key string is 24 characters long so that means it could not possibly be a 265 bit key.
(Do the math. 6 bits per character x 24 characters == 144 bits max. So assuming that some of those bits are "envelope" or "padding", that looks like it would be an 128 bit key.)
AES is a symmetric-key algorithm; see Wikipedia.

How come SecretKey.getEncoded() returns the plaintext password for PBE-generated secret keys?

I was experimenting with key derivation functions and I noticed that the secret keys I generate via all the PBE algorithms encode to the plain text password.
With that I mean that:
public class Main {
public static void main(String[] args) throws Exception {
byte[] salt = new byte[256/8];
SecureRandom.getInstanceStrong().nextBytes(salt);
KeySpec spec = new PBEKeySpec("password".toCharArray(), salt, /*iterations*/ 1000, /*key length*/ 1024);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHMACSHA512AndAES_256"); // PBE with HMAC SHA512 and AES_256
SecretKey secret = factory.generateSecret(spec);
System.out.println(new String(secret.getEncoded()));
}
}
prints password where I expected 1024 seemingly-random bytes. This doesn't quite add up for me.. can you explain it?
BTW: Note the same code does seem to work as I expect with PBKDF2 algorithms.
PS: In case it matters, I'm using vanilla OpenJDK 13 on mac (13.0.1.hs-adpt)
Encoded doesn't mean encrypted. As per Key class javadoc getEncoded() method returns a representation of the key:
* This is an external encoded form for the key used when a standard
* representation of the key is needed outside the Java Virtual Machine,
* as when transmitting the key to some other party. The key
* is encoded according to a standard format (such as
* X.509 {#code SubjectPublicKeyInfo} or PKCS#8), and
* is returned using the {#link #getEncoded() getEncoded} method.
Since PBEWithHMACSHA512AndAES_256 is a symmetric algorithm the observed behavior makes sense. The same key is used to perform both encryption and decryption, it can't be modified.
Taking a look at How do I properly use the “PBEWithHmacSHA512AndAES_256” algorithm? question. You need to encrypt the input, below byte[] messageBytes, with correct Cipher instance:
Cipher cipherEncrypt = Cipher.getInstance("PBEWithHMACSHA512AndAES_256");
cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherBytes = cipherEncrypt.doFinal(messageBytes);
byte[] iv = cipherEncrypt.getIV();

AES 256 decryption - Is IV safe to share?

Following on this question and its answer, I am creating an application that given a password string, will convert a plaintext and store its ciphertext, the salt generated and the initialization vector in a text file.
In the following code :
public String decrypt(CryptGroup cp) throws Exception {
String plaintext = null;
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, cp.getSalt(), ITERATIONS, KEY_SIZE);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(cp.getIv()));
plaintext = new String(cipher.doFinal(cp.getCipher()), "UTF-8");
return plaintext;
}
public CryptGroup encrypt(String plainText) throws Exception {
byte[] salt = generateSalt();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_SIZE);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
return new CryptGroup(ciphertext, salt, iv);
}
The CryptGroup object contains those 3 parameters (ciphertext, salt, iv : byte arrays for that matter).
Is it safe to store the initialization vector?
The answer in that question clearly states that the salt doesn't need to be secret, obviously the ciphertext can be also available, but what about the iv parameter?
Edit
If it is not safe to share, is it possible to retrieve the original iv from the salt alone?
Yes, IV's can be public information. You can use a calculation as IV, as long as you never use the combination of key and IV twice. In other words, you should be able to share just the salt, as long as you change the salt for each encryption.
Furthermore, for CBC it is required that IV "looks like random" to an attacker, especially when used for the same key. So a usual scheme is to use some output of PBKDF2 as IV data. Those particular bits should of course not be used to create the key as well, but it is OK to split the output size.
This has some drawbacks as PBKDF2 will use many more rounds if you request more than 160 bits of information (for SHA1). So you may concatenate the output of PBKDF2 and a counter (0 for the key, 1 for the IV) and use e.g. SHA256 to generate a 128 bit key (the 16 leftmost bytes) and 128 bit IV (the 16 rightmost bytes).
Let's explore some variants to this scheme:
You could also use a different hash such as SHA-512 to create a larger output and split it to get a key and IV, but beware that such a function may not be available everywhere. Java 8 should have "PBKDF2WithHmacSHA512" though (for the SecretKeyFactory).
You can also generate one PBKDF2 output and then use HKDF or HKDF-Expand to derive both a key and IV. Trouble is that HKDF / HKDF-Expand is not directly available from Java. Bouncy Castle does have that method though, because I send in an implementation of various KDF's.
Yet another way is to generate a new IV using SecureRandom, but in that case you need to store both the salt and IV. This might be useful if you need to encrypt multiple messages using the same key. In that case you can generate and store an IV for each separate message. This is a fine method if you are able to simply store 16 additional bytes.
In principle you could also use an all zero IV as long as you never reuse the same key (i.e. never reuse the same password/salt combination). However, in that case you might want to use AES-256 to avoid multi-target attacks.

Explanation to understand AES encryption code

I am creating a project to encrypt and decrypt a file. I have these two algorithms that work fine:
public static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
public static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static byte[] getRaw(String password_) throws Exception {
byte[] keyStart = password_.getBytes();
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
sr.setSeed(keyStart);
kgen.init(128, sr);
SecretKey skey = kgen.generateKey();
byte[] key = skey.getEncoded();
return key;
}
Now I need to explain how it works. Does it use a private key? Where is the key storage? Can anyone help me?
Note: see owlstead's answer for an excellent description of the flaws in your code example
Your encrypt() and decrypt() operations are performing AES encryption and decryption respectively, using Java's JCE libraries. A JCE provider will be selected to perform the actual cryptography - the provider chosen will be the first in the list of providers that offers an implementation of AES. You have defined the algorithm as only "AES", so the mode of operation and padding will be chosen by the provider. If you want to control this, use the form "AES/mode/padding" (see the docs for valid choices)
The getRaw method derives an AES key from a password. The raw bytes of the password provide the seed for a random number generator. The random number generator is then used to generate sufficient key material for a 128-bit AES key. A different password will produce a different seed, which should produce a different stream of random bytes and thus a different key. I suspect this approach is weakened by the lack of entropy present in most people's passwords, leading to a reduced key space and easier attacks.
There is no key storage in your example code. JCE keys are normally persisted using a KeyStore object and the storage mechanism is provider-dependent.
The above piece of code is a bunch of crap. Unfortunately it is frequently used as a code snippet for Android related code (Android code uses the same API as Java, so there is no need for an Android specific example, andt unfortunately it specifically fails on Android).
I'll explain the issues:
Using a SecureRandom as Password Based Key Derivation Function (PBKDF) is completely idiotic. The underlying implementation of the SecureRandom implementation may change. Furthermore, it is not specified by the SecureRandom that calling setSeed() as the first method will replace the seed; it may actually add the seed to the current state - and this is what certain newer android versions do.
Cipher.getInstance("AES") actually uses the provider defaults instead of specifying the mode of operation and padding mode for the given cipher. By default the Sun provider will use ECB mode which is not suitable for encrypting most data.
String.getBytes() - which is used for the password - returns the platform default encoding. Different platforms may have different default encodings. This means that different platforms will generate different keys.
Above code does not add a message authentication code (MAC or HMAC). This may lead to an attacker changing random ciphertext blocks, which leads to random plain text blocks. This may lead to loss of confidentiality as well if padding Oracle attacks apply.
It seems to me that you are a beginner in cryptography. Please use a higher level standard such as RNCryptor compatible code, or use a standard such as Cryptographic Message Syntax (CMS).

Problems encoding/decoding using AES-128-CBC

So basically I have these snippets of code and would like them to produce the same output:
require 'openssl'
aes = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
aes.key = "aaaaaaaaaaaaaaaa"
aes.iv = "aaaaaaaaaaaaaaaa"
aes.encrypt
encrypted = aes.update("1234567890123456") << aes.final
puts encrypted.unpack('H*').join
This prints:
8d3bbffade308f8e4e80cb77ecb8df19ee933f75438cec1315c4a491bd1b83f4
And this Java code:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
String key = "aaaaaaaaaaaaaaaa";
String textToEncryptpt = "1234567890123456";
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(textToEncryptpt.getBytes());
System.out.println(Crypto.bytesToHex(encrypted));
Prints:
2d3760f53b8b3dee722aed83224f418f9dd70e089ecfe9dc689147cfe0927ddb
Annoying thing is that it was working a couple of days ago... so I am not sure what happened. What's wrong with this code? Do you see anything unusual?
Ruby script is wrong. You have to first call the encrypt method, and then set the key and iv:
require 'openssl'
aes = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
aes.encrypt
aes.key = "aaaaaaaaaaaaaaaa"
aes.iv = "aaaaaaaaaaaaaaaa"
encrypted = aes.update("1234567890123456") << aes.final
puts encrypted.unpack('H*').join
I figured out because when trying to decode an encrypted string I got:
aescrypt.rb:13:in `final': bad decrypt (OpenSSL::Cipher::CipherError)
from aescrypt.rb:13:in `<main>'
Seems you found already the reason that your script does give different results.
Some more things to consider:
Don't ever hardcode the key in the program - that way you can't easily change it, and if someone gets access to your program code, she also gets to see the key.
Don't ever use a constant initialization vector. Instead, generate a random one and send it together with the ciphertext. Alternatively, if you generate the key from a password and some salt, you can also generate the IV from the same ... but don't use the key directly as IV.
Your key/IV values are strings, not bytes. String.getBytes() (in Java) converts the string to bytes using some encoding. The encoding used is system-dependent, and none of the usual String encodings (UTF-8, Latin-1, ...) can represent all bytes as printable (and typeable) characters. Preferably use something like Base64 or hex-encoding, if you have to store your key as string.
And whenever you transform a string to bytes, specify an encoding (and use the same encoding later for retrieving it).
#Cristian, For key and initial vector, you can create a function by using today's date plus the secure fixed keyword.
Eg: key = January 8, 2012 + Key
And for initial vector,
Eg: iv = January 8, 2012 + IV
Then enter that data(key and iv) to MD5 it will produce the output 16 bytes that you can use for the Key and IV. Every day, key and iv will change randomly.
Make sure both systems use the same date format and setup on the same date.

Categories

Resources