is there a way to decrypt files that have been encrypted using
openssl -des3 enc command.
Exactly how does openssl use the password and salt to make the key?
OpenSSL's enc utility uses a non-standard (and low quality) key derivation algorithm for passwords. The following code shows how the enc utility generates the key and initialization vector, given salt and a password. Note that enc stores the "salt" value in the encrypted file when the -salt option is specified (and that is critical for security).
public InputStream decrypt(InputStream is, byte[] password)
throws GeneralSecurityException, IOException
{
/* Parse the "salt" value from the stream. */
byte[] header = new byte[16];
for (int idx = 0; idx < header.length;) {
int n = is.read(header, idx, header.length - idx);
if (n < 0)
throw new EOFException("File header truncated.");
idx += n;
}
String magic = new String(header, 0, 8, "US-ASCII");
if (!"Salted__".equals(magic))
throw new IOException("Expected salt in header.");
/* Compute the key and IV with OpenSSL's non-standard method. */
SecretKey secret;
IvParameterSpec iv;
byte[] digest = new byte[32];
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password);
md5.update(header, 8, 8);
md5.digest(digest, 0, 16);
md5.update(digest, 0, 16);
md5.update(password);
md5.update(header, 8, 8);
md5.digest(digest, 16, 16);
iv = new IvParameterSpec(digest, 24, 8);
DESedeKeySpec keySpec = new DESedeKeySpec(digest);
SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
secret = factory.generateSecret(keySpec);
}
finally {
Arrays.fill(digest, (byte) 0);
}
/* Initialize the cipher. */
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, iv);
return new CipherInputStream(is, cipher);
}
This key and IV generation are described in the EVP_BytesToKey(3) documentation. The enc command uses 1 as the iteration count (which is a bad idea, and noted as a bug in the man page for my version of enc), and MD5 as the digest algorithm—a "broken" algorithm.
It is not clear how a OpenSSL converts text password to bytes. I'm guessing it uses the default platform character encoding. So, if you are stuck with a String password (not good, since it can't be "zero-ized"), you can just call password.getBytes() to convert it to a byte[].
If you can, use something like Java 6's Console or Swing's JPasswordField to get a password. These return an array, so you can "delete" the password from memory when you are done with it: Arrays.fill(password, '\0');
Thank you, Erickson, for your post. It helped me tremendously trying to recreate openssl's password to key and IV routine.
I ended up with something slightly different, probably because I need to decrypt blowfish-encrypted data rather than DES. See below.
Also I've discovered that openssl will stop reading passwords when it encounters bytes 00, 0a, or 0d. Generally I think that openssl only reads password characters between bytes 11 and 127. So for the example below, I have code that precedes this that truncates the password if it contains 00, 0a or 0d.
/* Compute the key and IV with OpenSSL's non-standard method. */
final byte[] digest = new byte[32];
final MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password, 0);
// append the salt
md5.update(salt);
// run the digest and output 16 bytes to the first 16 bytes to the digest array. Digest is reset
md5.digest(digest, 0, 16);
// write the first 16 bytes from the digest array back to the buffer
md5.update(digest, 0, 16);
// append the password
md5.update(password, 0);
// append the salt
md5.update(salt);
// run the digest and output 16 bytes to the last 16 bytes of the digest array
md5.digest(digest, 16, 16);
key = Arrays.copyOfRange(digest, 0, 16);
iv = Arrays.copyOfRange(digest, 16, 24);
This code above can be replaced with 3 lines using org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator. It becomes
final OpenSSLPBEParametersGenerator generator = new OpenSSLPBEParametersGenerator();
generator.init(password, salt);
final ParametersWithIV ivParam = (ParametersWithIV)generator.generateDerivedParameters(16, 8);
final KeyParameter keyParameter = (KeyParameter)ivParam.getParameters();
Related
I am building and Instagram bot which needs to login to Instagram. Now I know that Instagram uses an encryption system for its password, and I am trying to follow it but keep running into the
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: null
Basically Instagram needs a cipher text that follow the structure detailed in this github repo. My code is as follows to replicate it.
// Generate a random IV
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
// Get the system current time in milliseconds
long timestamp = System.currentTimeMillis();
// Converting public key to byte array from hex string
byte[] publicKeyBytes = hexStringToByteArray(publicKey);
// Create a random key
byte[] randomKey = new byte[32];
new SecureRandom().nextBytes(randomKey);
// rebuild key using SecretKeySpec
SecretKey publicKeyClass = new SecretKeySpec(randomKey,0, randomKey.length, "AES");
//generate the cipher text
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKeyClass, new GCMParameterSpec(128, iv));
// Add the current time in milliseconds as additional authenticated data
cipher.updateAAD(Long.toString(timestamp).getBytes(StandardCharsets.UTF_8));
byte[] cipherText = cipher.doFinal(pwd.getBytes(StandardCharsets.UTF_8));
// Get the tag from the end of the cipher text
byte[] tag = Arrays.copyOfRange(cipherText, cipherText.length - 16, cipherText.length);
// Now we encrypt a random key using the same public key
// use RSA encryption to encrypt the random key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes, "RSA");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey encryptedKeyClass = keyFactory.generatePublic(keySpec);
Cipher cipher2 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher2.init(Cipher.ENCRYPT_MODE, encryptedKeyClass);
byte[] encryptedRandomKey = cipher2.doFinal(randomKey);
// Generate the final encrypted text
String encryptedText = publicKeyId+encryptedRandomKey.toString()+tag.toString()+cipherText.toString();
//Base64 encode the encrypted text
String encryptedTextBase64 = Base64.getEncoder().encodeToString(encryptedText.getBytes(StandardCharsets.UTF_8));
and the function hexStringtoByteArray
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] =(byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
I realize that it is a lot of code so here is a basic gist of what is going on
I pass a password and publickey(string) to the function.
We generate a random IV and a new random key.
I then use this random key to encrypt the password using AES_GCM_256 bit encryption.
Then the publickey(which was given by instagram) is 64 bytes long and appears to be hex encoded. So I decode it and use it as a key to encrypt the randomly generated key that I have created.
Finally I combine all of this in the format that insta wants and return the final cipher text.
Now my problem lies in the 4th step.I keep getting the error that the key is of invalid spec. The line that throws the error is
PublicKey encryptedKeyClass = keyFactory.generatePublic(keySpec);
Example Public Key that Instagram returns
c81814218a8fe89a6a5794ff5f2a192bf5ab9d3f7115bc8fbceb7b701079777c
I checked multiple online sources and they all seem to follow the same method I am doing. Am I doing something wrong? Thanks !
I have a java code working perfectly
public static String encrypt(String message, String sercretKey)
{
String base64EncryptedString = "";
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digestOfPassword = md.digest(sercretKey.getBytes("utf-8"));
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
byte[] iv = Arrays.copyOf(digestOfPassword, 16);
SecretKey key = new SecretKeySpec(keyBytes, "AES");
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] plainTextBytes = message.getBytes("utf-8");
byte[] buf = cipher.doFinal(plainTextBytes);
byte[] base64Bytes = Base64.getEncoder().encode(buf);
base64EncryptedString = new String(base64Bytes);
return base64EncryptedString;
}
I have tried using below code to recreate this above code in PHP
function encryptTest($sSecretKey,$sValue)
{
$key = hash('sha256', $sSecretKey,false);
$key = utf8_encode($key);
$key = substr($key, 0, 24);
$iv = substr($key, 0, 16);
$data = $sValue;
$outEnc = openssl_encrypt($data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($outEnc);
}
But showing different results. What I have missed.
(Same types of questions are available in StackOverflow, but pointing my issues)
There are the following issues:
In the PHP code, the key is currently returned hex encoded. Instead, it must be returned as bytes string. To do this, the third parameter in hash() must be switched from false to true.
In the Java code a 192 bits key is used, i.e. AES-192. Accordingly, in the PHP code "AES-192-CBC" must be applied (and not "AES-256-CBC").
The utf8_encode() call in the PHP code is to be removed, as this corrupts the key.
With these changes, both codes provide the same ciphertext.
Security:
Using SHA256 as key derivation is insecure. Instead apply a dedicated algorithm like Argon2 or PBKDF2. Also, using the key (or a part of it) as IV is insecure as it results in the reuse of key/IV pairs. Instead, a randomly generated IV should be applied for each encryption.
I have used AES encryption in java as below and trying to decrypt in javascript
JAVA :
byte[] input = "data".getBytes();
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] thedigest = md.digest("ENCRYPTION_KEY".getBytes("UTF-8"));
SecretKeySpec skc = new SecretKeySpec(Arrays.copyOf(thedigest, 16), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
String encryptData = Base64.getUrlEncoder().encodeToString(cipherText);
// Base64.encodeToString(cipherText, Base64.DEFAULT);
System.out.println("encryptString:.......... "+encryptData);
NODEJS:
var crypto = require('crypto');
let keyStr = "ENCRYPTION_KEY";
var text = 'infodba';
var hashPwd =crypto.createHash('sha256').update(keyStr,'utf8').digest();
var key=[] ;
for (i = 0; i < 16; i++) {
key[i] = hashPwd[i];
}
var keyBuffer = new Buffer(hashPwd);
var decipherIV = crypto.createDecipheriv('aes-256-ecb', keyBuffer,'');
var cipherTxtBuffer = new Buffer('encryptedData','hex');
var output = decipherIV.update(cipherTxtBuffer);
output= output+decipherIV.final();
console.log("Dec Output "+output);
While running the node JS code getting error
internal/crypto/cipher.js:174
const ret = this[kHandle].final(); ^
Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
at Decipheriv.final (internal/crypto/cipher.js:174:29)
at deciphers (D:\TestJS\index.js:32:30)
The Java code uses only the first 16 bytes of the SHA256 hash as key, so the hash has to be truncated accordingly:
var keyStr = 'ENCRYPTION_KEY';
var hashPwd = crypto.createHash('sha256').update(keyStr, 'utf8').digest();
var keyBuffer = Buffer.from(hashPwd.subarray(0, 16));
A 16 bytes key corresponds to AES-128, so 'aes-128-ecb' has to be used for decryption (and not 'aes-256-ecb'). Furthermore, the ciphertext is Base64url encoded, so it has to be Base64url decoded (and not hex decoded):
var encryptedData = 'dkcvstQcGMQUJ1EJbHs3eY6j_0DjWqYTDGmedmUZwWs=';
var decipherIV = crypto.createDecipheriv('aes-128-ecb', keyBuffer,'');
var output = Buffer.concat([decipherIV.update(encryptedData, 'base64url'), decipherIV.final()]);
output is a buffer and may have to be UTF-8 decoded:
console.log(output.toString('utf8')); // §3.1: The fee is $3,000.
With these changes, the NodeJS code decrypts the ciphertext created with the Java code.
Security:
The ECB mode is insecure and should not be used. Nowadays authenticated encryption is usually applied (such as GCM), or at least a mode with an IV (such as CBC).
Using a (fast) digest like SHA256 as key derivation is insecure. More secure is a dedicated key derivation function like Argon2 or PBKDF2.
In the Java code, the Cipher object is instantiated with the algorithm ("AES") alone. This is not robust because then the provider-dependent default values are applied for mode and padding. For SunJCE, these are ECB and PKCS#5 padding, to which the NodeJS code is tailored. For other providers this may differ, so that the NodeJS code would then no longer be compatible. Therefore, it should be fully specified, e.g. "AES/ECB/PKCS5Padding".
Similarly, when encoding the plaintext with getBytes(), the encoding should be specified, otherwise the platform-dependent default encoding is used.
I have a PHP encryption function. I need a java counter part for the same. Due to my limited knowledge in PHP I am unable to understand. Some one knows both the language, kindly help.
PHP code:
function encrypt($decrypted, $keyvalue) {
// Build a 256-bit $key which is a SHA256 hash of $keyvalue.
$key = hash('SHA256', $keyvalue, true);
// Build $iv and $iv_base64. We use a block size of 128 bits (AES compliant) and CBC mode. (Note: ECB mode is inadequate as IV is not used.)
srand(); $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
if (strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) return false;
// Encrypt $decrypted and an MD5 of $decrypted using $key. MD5 is fine to use here because it's just to verify successful decryption.
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $decrypted . md5($decrypted), MCRYPT_MODE_CBC, $iv));
// We're done!
return $iv_base64 . $encrypted;
}
Thanks in advance
Aniruddha
This should do it.
public static byte[] encrypt(byte[] decrypted, byte[] keyvalue) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] key = sha256.digest(keyvalue);
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] checksum = md5.digest(decrypted);
//The length of the value to encrypt must be a multiple of 16.
byte[] decryptedAndChecksum = new byte[(decrypted.length + md5.getDigestLength() + 15) / 16 * 16];
System.arraycopy(decrypted, 0, decryptedAndChecksum, 0, decrypted.length);
System.arraycopy(checksum, 0, decryptedAndChecksum, decrypted.length, checksum.length);
//The remaining bytes of decryptedAndChecksum stay as 0 (default byte value) because PHP pads with 0's.
SecureRandom rnd = new SecureRandom();
byte[] iv = new byte[16];
rnd.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivSpec);
byte[] encrypted = Base64.encodeBase64(cipher.doFinal(decryptedAndChecksum));
byte[] ivBase64 = Base64.encodeBase64String(iv).substring(0, 22).getBytes();
byte[] output = new byte[encrypted.length + ivBase64.length];
System.arraycopy(ivBase64, 0, output, 0, ivBase64.length);
System.arraycopy(encrypted, 0, output, ivBase64.length, encrypted.length);
return output;
}
The equivalent of MCRYPT_RIJNDAEL_128 and MCRYPT_MODE_CBC in java is AES/CBC/NoPadding. You also need a utility for Base64 encoding, the above code uses Base64 from the Apache Codec library.
Also, because the encryption key is 256 bits, you'll need the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files. These can be downloaded from Oracle's website.
Finally, do heed ntoskrnl's warning. This encryption really could be better, don't copy-paste from the PHP manual.
I need to translate the below C# codes into Java, however, I could not find any Java equivalent to the Rfc2898DerivedBytes and Rijndael of C#.
private static string Encrypt(string sData, string sEncryptionKey)
{
string str = null;
string str2;
try
{
Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(sEncryptionKey, 8);
Rijndael rijndael = Rijndael.Create();
rijndael.IV = bytes.GetBytes(rijndael.BlockSize / 8);
rijndael.Key = bytes.GetBytes(rijndael.KeySize / 8);
byte[] buffer = Encoding.Unicode.GetBytes(sData);
using (MemoryStream stream = new MemoryStream())
{
using (CryptoStream stream2 = new CryptoStream(stream, rijndael.CreateEncryptor(), CryptoStreamMode.Write))
{
stream.Write(bytes.Salt, 0, bytes.Salt.Length);
stream2.Write(buffer, 0, buffer.Length);
stream2.Close();
str = Convert.ToBase64String(stream.ToArray());
str2 = str;
}
}
}
catch (Exception exception)
{
System.out.println(exception.getMessage());
}
return str2;
}
[Update]
I need to use this function to encrypt the password for new created user, and the encrypted password should also be correctly decrypted by other invoker including C#.
I follow the documents which list in the comments and answer, and try to write below simply sample for quickly verification.
public class testEncrypt {
public static void main(String[] args) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
char[] password = "passkey".toCharArray();
SecureRandom random = new SecureRandom();
byte[] salt = new byte[8];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec(password, salt, 1000, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.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("301a7fed-54e4-4ae2-9b4d-6db057f75c91".getBytes("UTF-8"));
System.out.println(ciphertext.length);
}
}
However, the length of the ciphertext is 48, but actually in C#, it looks like this format
WHUNV5xrsfETEiCwcT0M731+Ak1jibsWEodJSaBraP1cmmkS1TpGWqwt/6p/a7oy8Yq30ImZPbFF+Y0JNLa3Eu2UGuazZtuhEepUIIdaDEtA2FO0JYIj2A==
total 120 characters.
Is there something wrong with the code?
RFC2898 is the official name for PBKDF2 (Password Based Key Derivation Function).
This question seems to use the SecretKeyFactory class for PBKDF2.
Password Verification with PBKDF2 in Java
If you cannot find any implementation that you are satisfied with, I suggest you take a look at my question where I used a few classes from BouncyCastle (for C#, but should work for Java) and created the algorithm. I had to create this for C# because there was no Rfc2898DeriveBytes for the .NET Compact Framework.
This question should definitely help you too!
You can also find an implementation here that was done by someone who stumbled across your same problem.
Also to answer the second part of your question,
Rijndael doesn't differ much from AES. To quote this webpage
Namely, Rijndael allows for both key and block sizes to be chosen
independently from the set of { 128, 160, 192, 224, 256 } bits. (And
the key size does not in fact have to match the block size). However,
FIPS-197 specifies that the block size must always be 128 bits in AES,
and that the key size may be either 128, 192, or 256 bits.
Rijndael algorithm was chosen by the NIST to be the Advanced Encryption algorithm.
So you can use the AES algorithm in Java.