I have encrypted a file by using OpenSSL aes-256-gcm. As aes-256-gcm not directed supported by command line I have installed LibreSSL and I am able to use the below command to encrypt the data of a file.
openssl enc -aes-256-gcm -K 61616161616161616161616161616161 -iv 768A5C31A97D5FE9 -e -in file.in -out file.out
I need to decrypt the data of file.out in Java and I am unable to do that.
Sample code :
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
String key = "61616161616161616161616161616161";
byte[] IV = "768A5C31A97D5FE9".getBytes();
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText); // for the data by reading file.out
However, I am getting an exception saying javax.crypto.AEADBadTagException: Tag mismatch!
That should't work. Commandline openssl enc doesn't support AEAD ciphers/modes, although early versions of 1.0.1 (below patch h, in 2012-2014) failed to catch if you incorrectly specified such a cipher and silently produced wrong output. If you are actually using LibreSSL and not OpenSSL, it appears to have inherited this problem and not fixed it, even though the whole point of the LibreSSL project was that they were going to fix all the bugs caused by the incompetent OpenSSL people.
If this were a cipher that worked correctly in OpenSSL (and also Java), like aes-256-ctr, then your only problem would be that openssl enc -K -iv take their arguments in hex (suitable for a shell context), whereas Java crypto is called from code that can handle binary data and expects its arguments in that form. As a result the values you provide to OpenSSL are actually 16 bytes (128 bits) and 8 bytes (64 bits), not 256 bits and 128 bits as they should be (for CTR; for GCM an IV of 96 bits would be correct, but as noted GCM won't work here). openssl enc automatically pads -K -iv with (binary) zeros, but Java doesn't. Thus you would need something more like
byte[] key = Arrays.copyOf( javax.xml.bind.DatatypeConverter.parseHexBinary("61616161616161616161616161616161"), 32);
// Arrays.copyOf zero-pads when expanding an array
// then use SecretKeySpec (key, "AES")
// and IVParameterSpec (iv) instead of GCMParameterSpec
// but after Java8 most of javax.xml is removed, so unless you
// are using a library that contains this (e.g. Apache)
// or have already written your own, you need something like
byte[] fromHex(String h){
byte[] v = new byte[h.length()/2];
for( int i = 0; i < h.length(); i += 2 ) v[i] = Integer.parseInt(h.substring(i,i+2),16);
return v;
}
Compare AES encrypt with openssl command line tool, and decrypt in Java and Blowfish encrypt in Java/Scala and decrypt in bash (the latter is the reverse direction, but the need to match is the same)
Related
The file is encrypted using the following command:
openssl enc -aes-256-cbc -in file.txt -out file_enc.txt -k 1234567812345678
The file is decrypted using the following command:
openssl enc -d -aes-256-cbc -in file_enc.txt -out file.txt -k 1234567812345678
After printing the salt and key in java I get:
Key=b796fbb416732ce13d39dbb60c0fb234a8f6d70e49df1c7e62e55e81d33a6bff774254ac99268856bf3afe0b95defdad
and in cmd I get :
salt=2D7C7E1C84BD6693
key=B796FBB416732CE13D39DBB60C0FB234A8F6D70E49DF1C7E62E55E81D33A6BFF
iv =774254AC99268856BF3AFE0B95DEFDAD
after running :
openssl enc -aes-256-cbc -in file.txt -out file_enc.txt -pbkdf2 -k 1234567812345678 -p
I am using the following code but the encrypted file is printing :
public static void main(String args[]) throws InvalidKeySpecException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
InvalidKeyException,
BadPaddingException,
InvalidAlgorithmParameterException,
NoSuchPaddingException,
IOException {
String password = "1234567812345678";
String algorithm = "AES/CBC/PKCS5Padding";
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
Resource resource = new ClassPathResource("file_enc.txt");
File inputFile = resource.getFile();
byte[] salt = new byte[8], data = new byte[1024], tmp;
int keylen = 32, ivlen = 16, cnt;
try( InputStream is = new FileInputStream(inputFile) ){
if( is.read(salt) != 8 || !Arrays.equals(salt, "Salted__".getBytes() )
|| is.read(salt) != 8 ) throw new Exception("salt fail");
byte[] keyandIV = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256")
.generateSecret( new PBEKeySpec(password.toCharArray(), salt, 10000, (keylen+ivlen)*8)
).getEncoded();
System.out.println("Key "+ byteArrayToHex(keyandIV));
Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyandIV,0,keylen,"AES"),
new IvParameterSpec(keyandIV,keylen,ivlen));
while( (cnt = is.read(data)) > 0 ){
if( (tmp = ciph.update(data, 0, cnt)) != null ) System.out.write(tmp);
}
tmp = ciph.doFinal(); System.out.write(tmp);
}
}
Your getKeyFromPassword creates a SecretkeyFactory for PBKDF2 (with HmacSHA256) but doesn't use it; instead you use the password as the key, which is wrong -- unless you actually wanted to do openssl enc with a key (-K uppercase and hex which should be 64 hexits/32 bytes for AES-256) and not a password (-k lowercase and any characters or any length). (Below IIRC 18, String.getBytes() gives you a JVM-dependent encoding which for an arbitrary string like a real password can vary on different systems or environments, which will cause cryptography using it to fail, but for the example string you show all realistic encodings give the same result.)
But even if you did use this factory it wouldn't be correct because openssl enc by default does not use PBKDF2, it uses a function called EVP_BytesToKey which is based on but different from PBKDF1. In OpenSSL 1.1.1 and 3.0 only, you can optionally specify -pbkdf2 (and/or -iter N) in enc, and if you don't it gives a warning message about 'deprecated key derivation' which you should have noticed, so I assume you're either using obsolete OpenSSL or trying to prevent us accurately understanding your situation and thus making it unlikely to get a useful answer.
Also, with either kind of key derivation (old EVP_BytesToKey or optional new PBKDF2) openssl enc by default uses salt, which is randomly generated and written in the file when encrypting; when decrypting you must read the salt from the file and use it. Specifically, skip or discard the first 8 bytes (which are the fixed characters Salted__) and take the next 8 bytes as salt, then the remainder of the file as ciphertext
Depending on what you want (or maybe your users/customers/etc want) to do there are three possibilities:
encrypt with openssl enc -aes-256-cbc -k ... with the default derivation (as now) and code the Java to read the salt from the file as above, implement EVP_BytesToKey using the password and that salt, and use its output for both the key and IV in the Java Cipher (for aes-256-cbc generate 48 bytes and use the first 32 bytes as key and the last 16 bytes as IV). EVP_BytesToKey uses a hash which defaults to SHA256 for OpenSSL 1.1.0 up and MD5 for lower versions, so you need to know which version did the encryption for this to work, or else you can specify the hash on the enc command with -md $hash. There have been hundreds of Qs about this going back over a decade; search for EVP_BytesToKey to find some of them.
encrypt with openssl enc -aes-256-cbc -pbkdf2 -k ... and code the Java to read the salt from the file as above and use the keyfactory you created to generate 48 bytes of 'key' material, which you must actually split into key and IV as above in the Java Cipher.
encrypt with openssl enc -aes-256-cbc -K 64hexits -iv 32hexits and code the Java to use the corresponding binary key and IV values.
In command i didn't specify neither random IV nor PKCS5Padding
When you use either old or new key derivation in openssl enc it derives the IV rather than specifying it separately; only if you use explicit key (-K uppercase) do you also specify -iv. openssl enc always defaults to the padding variously called pkcs5, pkcs7, or pkcs5/7, except when no padding is needed (stream ciphers like RC4 or ChaCha or stream modes like CTR, OFB, CFB).
Okay, you seem to be reading only about half of what I said. Most fundamentally, you still have openssl enc without -pbkdf2 but are trying to decrypt in Java with PBKDF2, which is flat wrong. In addition you are reading the salt but then converting it to hex, which is wrong, the salt from the file is the correct salt, and you are generating a completely bogus random IV, not deriving it as I said.
To be concrete, if you (or I) encrypt a file with -pbkdf2 like
openssl enc -aes-cbc-256 -pbkdf2 -k 1234567812345678
which will only work on OpenSSL 1.1.1 or 3.0 (i.e. since 2018), the following (minimalistic) Java code correctly decrypts it:
static void SO73456313OpensslEnc2_Java (String[] args) throws Exception {
// file pw: decrypt openssl(1.1.1+) enc -aes-256-cbc -pbkdf2 -k $pw
byte[] salt = new byte[8], data = new byte[1024], tmp;
int keylen = 32, ivlen = 16, cnt;
try( InputStream is = new FileInputStream(args[0]) ){
if( is.read(salt) != 8 || !Arrays.equals(salt, "Salted__".getBytes() )
|| is.read(salt) != 8 ) throw new Exception("salt fail");
byte[] keyandIV = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256")
.generateSecret( new PBEKeySpec(args[1].toCharArray(), salt, 10000, (keylen+ivlen)*8)
).getEncoded();
Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyandIV,0,keylen,"AES"),
new IvParameterSpec(keyandIV,keylen,ivlen));
while( (cnt = is.read(data)) > 0 ){
if( (tmp = ciph.update(data, 0, cnt)) != null ) System.out.write(tmp);
}
tmp = ciph.doFinal(); System.out.write(tmp);
}
}
Note in PBEKeySpec I used itercount=10000 which is the enc default. You can use a higher number like 65536, which may be desirable for security (but that part is offtopic here), if you specify it when encrypting like:
openssl enc -aes-256-cbc -pbkdf2 -iter 65536 -k ...
OTOH if you use the command you posted, which you must on OpenSSL 1.1.0 or lower, then you cannot decrypt using PBKDF2 at all.
For that case instead see
How to decrypt file in Java encrypted with openssl command using AES?
How to decode a string encoded with openssl aes-128-cbc using java?
Java equivalent of an OpenSSL AES CBC encryption
Java AES Decryption with keyFile using BouncyCastle SSL
and CryptoJS AES encryption and Java AES decryption (cryptojs is sometimes compatible with OpenSSL, including the case in that Q).
And remember, as noted in at least some of those earlier Qs, the command you posted uses EVP_BytesToKey with SHA256 in 1.1.0 up but MD5 in 1.0.2 and lower, so you need to know which OpenSSL was or will be used.
I am provided two files encrypted_key.enc and encrypted_data.csv.enc. I need to use my private key to decrypt the encrypted_key.enc to get a symmetric key and then use that symmetric key to decrypt the encrypted_data.csv.enc file.
On the terminal, the following commands get the job done:
openssl rsautl -decrypt -ssl -inkey my_private_key -in encrypted_key.enc -out key
openssl aes-256-cbc -d -in encrypted_data.csv.enc -out secret.txt -pass file:key
My goal is to perform the java equivalent of the two commands. I was able to successfully decrypt the first file and retrieve the symmetric key.
Now I'm unable to use that symmetric key to decrypt the csv file. My issue arises in the decipher.init(Cipher.DECRYPT_MODE, keySpec); I receive the following stacktrace
Exception in thread "main" java.security.InvalidKeyException: Illegal key size or default parameters
I'm unclear on what exactly I'm missing from the decryption process. I've tried changing the cipher provider but that didn't help. Other posts have posted solutions using an IVParameterSpec but my decryption case doesn't seem to need it or I'm confused on where to put it.
File file = new File("my_private_key");
PrivateKey pk = getPrivateKey(file);
// Decrypt secret key
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, pk);
File skFile = new File("encrypted_key.enc");
FileInputStream fileInputStream = new FileInputStream(skFile);
byte[] decodedBytes = IOUtils.toByteArray(fileInputStream);
byte[] original = cipher.doFinal(decodedBytes);
String decodedOriginal = new String(Base64.encodeBase64(original));
System.out.println(decodedOriginal);
// Use the secret key for decrypting file
File csvFile =
new File(
"encrypted_data.csv.enc");
FileInputStream csvIS = new FileInputStream(csvFile);
Cipher decipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(original, "AES");
decipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] csvOriginal = decipher.doFinal(IOUtils.toByteArray(csvIS));
String csvContents = new String(csvOriginal);
System.out.println(csvContents);
Before Java 1.8 (I think, somewhere around there) you are limited by the Java Unlimited Strength Policy for key sizes above 128-bits. This is the most likely cause of the exception you are getting.
Unfortunately this won't fix your code. openssl with the pass flag uses an insecure KDF named EVP_BytesToKey(). Java doesn't natively support this KDF. You don't want to use it anyway since it is insecure. Update the upstream code to use a better KDF like PBKDF2. There is native support for this in Java.
Further, you're using CBC mode in openssl and ECB mode in Java. And you aren't specifying an IV in openssl. I get the impression you didn't write the Java code yourself. You might benefit from taking the time to learn and research what is actually happening in your code and in the commands you are executing and you might be better equipped to solve the problem.
at the moment im trying to encrypt with rsa in php with a public key generated in an android app and then decrypt in android app again.
My code to generate the keys in android is:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
With that keys i can en- and decrypt very well. The pub key looks like this:
OpenSSLRSAPublicKey{modulus=9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13,publicExponent=10001}
In php im taking the modulus and exponent, creating a encrypted string with phpseclib 1.0
$rsa = new Crypt_RSA();
// $rsa->createKey();
$m = "9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13";
$e = "10001";
$data = "hallo";
$modulus = new Math_BigInteger($m, 16);
$exponent = new Math_BigInteger($e, 16);
$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
$messageEncrypt = $rsa->encrypt($data);
In Android again, im loading the key, and decrypting it like this:
Cipher cipher1 = Cipher.getInstance("RSA");
cipher1.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher1.doFinal(encrypted.getBytes());
String decrypted = new String(decryptedBytes);
Im always getting a wrong decrypted plaintext or a " Caused by: java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" error message from Android.
What i think: The problem is the encoded transfer. That php outputs a different encoded version as java uses. So I tried a lot of different ways. I tried to convert the output to String/bin/hex/byte. Then transfer it, with socket or with copy+paste directly in the Code. Convert it back from hex/bin... to a byte[] and try to decode it. Nothing works...
Anyone has a solution for this?
Since you're not specifying the encryption mode with phpseclib what that means is that you're using the (more secure and less common) OAEP encryption mode. My guess is that Java is using PKCS1 encryption by default ($rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);).
That said, with OAEP mode and the key that you're using (a 1024-bit key; 128 bytes), the limit is 86 bytes. The limit with PKCS1 mode is 117 bytes.
phpseclib 1.0 / 2.0 might not give errors because phpseclib tries to be all user friendly and will split the string up into chunks of the max size and will encrypt each chunk separately. It's unlikely that Java does that.
I have observed the following when I worked with Cipher.
Encryption code:
Cipher aes = Cipher.getInstance("AES");
aes.init(Cipher.ENCRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
Decryption code :
Cipher aes = Cipher.getInstance("AES");
aes.init(Cipher.DECRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
I get IllegalBlockSizeException ( Input length must be multiple of 16 when ...) on running the Decrypt code.
But If I change the decrypt code to
Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding"); //I am passing the padding too
aes.init(Cipher.DECRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
It works fine.
I understand that it is in the pattern algorithm/mode/padding. So I thought it is because I didn't mention the padding. So I tried giving mode and padding during encryption,
Encryption code:
Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding");//Gave padding during encryption too
aes.init(Cipher.ENCRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
Decryption code :
Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding");
aes.init(Cipher.DECRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
But it fails with IllegalBlockSizeException.
What is the reason, why the exception and what is exactly happening underneath.
If anyone can help? Thanks in advance
UPDATE
Looks like the issue is with the string I am encrypting and decrypting. Because, even the code that I said works, doesn't always work. I am basically encrypting UUIDs (eg : 8e7307a2-ef01-4d7d-b854-e81ce152bbf6). It works with certain strings and doesn't with certain others.
The length of encrypted String is 64 which is divisible by 16. Yes, I am running it on the same machine.
Method for secret key generation:
private Key generateKey() throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA");
String passphrase = "blahbl blahbla blah";
digest.update(passphrase.getBytes());
return new SecretKeySpec(digest.digest(), 0, 16, "AES");
}
During decryption, one can only get an IllegalBlockSizeException if the input data is not a multiple of the block-size (16 bytes for AES).
If the key or the data was invalid (but correct in length), you would get a BadPaddingException because the PKCS #5 padding would be wrong in the plaintext. Very occasionally the padding would appear correct by chance and you would have no exception at all.
N.B. I would recommend you always specify the padding and mode. If you don't, you are liable to be surprised if the provider changes the defaults. AFAIK, the Sun provider converts "AES" to "AES/ECB/PKCS5Padding".
Though I haven't fully understood the internals, I have found what the issue is.
I fetch the encrypted string as a GET request parameter. As the string contains unsafe characters, over the request the string gets corrupted. The solution is, to do URL encoding and decoding.
I am able to do it successfully using the URLEncoder and URLDecoder.
Now the results are consistent. Thanks :)
I would be grateful if anyone can contribute more to this.
I have a bash script which uses openssl to encrypt data, and Java code which decrypts the result. Based on my earlier post, I'm now able to enter a password in openssl, and copy the resulting key/iv into Java. This relies on using the -nosalt option in openssl. I'd like to remove that option, and take password/salt/iv from openssl and pass it into a JDK key derivation function.
Here's the openssl script I'm using:
#!/bin/bash
openssl enc -aes-128-cbc -in test -out test.enc -p
When I run this, and enter a password, it prints out the following for example.
salt=820E005048F1DF74
key=16023FBEB58DF4EB36229286419F4589
iv=DE46F8904224A0E86E8F8F08F03BCC1A
When I try the same password/salt/iv in Java, I'm not able to decrypt test.enc. I tried Java code based on the answer by #erickson in this post. Here's the snippet.
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 1024, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
If I print the "secret" that's generated, it's not the same as the "key" that openssl printed. Do I need to change one of the Java parameters to match how openssl is deriving its key?