I am attempting to implement this encryption scheme with AES encryption.
Basically it goes as follows:
user data is encrypted with a surrogate key (surrogateKey)
the surrogate key is XORed with a key derived from the password
(passwordKey) and stored (storedKey)
when the key is needed (to encrypt or decrypt user data) the storedKey is retrieved from the DB and XORed again with the freshly generated passwordKey to recover the surrogateKey
Except, I must be doing something wrong in implementation, because I can never seem to recover a valid surrogateKey and any attempt at decryption is giving me a BadPaddingException.
The code below demonstrates the issue. Sorry if it's a bit long, but you should be able to just copy and paste it into your IDE.`
import java.io.ByteArrayOutputStream;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class SurrogateTest {
private static final String alphanumeric =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "1234567890"
+ "!#$%&()+*<>?_-=^~|";
private static String plainText = "I am the very model of a modern major general";
private static String cipherText = "";
private static SecureRandom rnd = new SecureRandom();
private static byte[] salt;
public static void main(String[] args) {
// arguments are password and recovery string
if (args.length > 1) {
System.out.println("password: " + args[0] + "; recovery: " + args[1]);
}
String password = args[0];
// passwordKey
SecretKey passwordKey = getKey(password);
System.out.println("passwordKey: " + DatatypeConverter.printBase64Binary(passwordKey.getEncoded()));
// Generate surrogate encryption key from random string
String rand = randomString(24);
SecretKey surrogateKey = getKey(rand);
byte[] surrogateByteArray = surrogateKey.getEncoded();
System.out.println("surrogate: " + DatatypeConverter.printBase64Binary(surrogateByteArray));
// encrypt plainText
System.out.println("text to encrypt: " + plainText);
cipherText = encryptWithKey(plainText, surrogateKey);
// XOR surrogateKey with passwordKey to get storedKey
SecretKey storedKey = xorWithKey(surrogateKey, passwordKey);
String storedKeyString = DatatypeConverter.printBase64Binary(storedKey.getEncoded());
System.out.println("storedKey: " + storedKeyString);
byte[] storedKey2Array = DatatypeConverter.parseBase64Binary(storedKeyString);
SecretKey storedKey2 = new SecretKeySpec(storedKey2Array, 0, storedKey2Array.length, "AES");
String storedKey2String = DatatypeConverter.printBase64Binary(storedKey2.getEncoded());
System.out.println("storedKey->String->key->string: " + storedKey2String);
// recover surrogateKey from storedKey2
SecretKey password2Key = getKey(password);
System.out.println("password2Key: " + DatatypeConverter.printBase64Binary(password2Key.getEncoded()));
SecretKey surrogate2Key = xorWithKey(storedKey2, password2Key);
System.out.println("surrogate2 (recovered): " + DatatypeConverter.printBase64Binary(surrogate2Key.getEncoded()));
// decrypt text
String decryptedText = decryptWithKey(cipherText, surrogate2Key);
System.out.println("decryptedText: " + decryptedText);
}
private static SecretKey xorWithKey(SecretKey a, SecretKey b) {
byte[] out = new byte[b.getEncoded().length];
for (int i = 0; i < b.getEncoded().length; i++) {
out[i] = (byte) (b.getEncoded()[i] ^ a.getEncoded()[i % a.getEncoded().length]);
}
SecretKey outKey = new SecretKeySpec(out, 0, out.length, "AES");
return outKey;
}
private static String randomString(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
sb.append(alphanumeric.charAt(rnd.nextInt(alphanumeric.length())));
return sb.toString();
}
// return encryption key
private static SecretKey getKey(String password) {
try {
SecureRandom random = new SecureRandom();
salt = new byte[16];
random.nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// obtain secret key
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
return secret;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String encryptWithKey(String str, SecretKey secret) {
try {
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[] encryptedText = cipher.doFinal(str.getBytes("UTF-8")); // encrypt the message str here
// concatenate salt + iv + ciphertext
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(salt);
outputStream.write(iv);
outputStream.write(encryptedText);
// properly encode the complete ciphertext
String encrypted = DatatypeConverter.printBase64Binary(outputStream.toByteArray());
return encrypted;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String decryptWithKey(String str, SecretKey secret) {
try {
byte[] ciphertext = DatatypeConverter.parseBase64Binary(str);
if (ciphertext.length < 48) {
return null;
}
salt = Arrays.copyOfRange(ciphertext, 0, 16);
byte[] iv = Arrays.copyOfRange(ciphertext, 16, 32);
byte[] ct = Arrays.copyOfRange(ciphertext, 32, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] plaintext = cipher.doFinal(ct);
return new String(plaintext, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Any ideas what I'm doing wrong?
Running the posted code shows that surrogate2 (recovered) is different from surrogate. It should be obvious this is badly wrong. The reason is that at encryption you derive the 'password' (wrapping) key using a random salt and write that salt at the beginning of your data blob; at decryption you derive the unwrapping key using a new salt and then read the correct salt from the blob and totally ignore it. This means your unwrapping key is wrong, so your unwrapped data key is wrong, so your decryption is wrong.
PS: the random key used to directly encrypt and decrypt the data is usually called a 'data' key (as I just did) or DEK (abbreviation for Data Encryption or Encrypting Key), or a more specific term like 'session key' or 'message key' (in this case), or 'working key' or 'transient key' to emphasize its limited scope. It is not usually called 'surrogate'. And using PBKDF2 to derive this data key from a strongly random string is a waste of time; just use SecureRandom_instance.nextBytes(byte[]) directly for the data key.
Related
I am trying to convert my java code to NodeJs code. It's a little more complicate because the customised format included the password and salt.
In main method there is one example.
Here is my java code:
public class App {
private static final int DYN_SALT_LENGTH = 10;
private static final int ITERATION_COUNT = 65556;
private static final int KEY_LENGTH = 256;
private static final String SECRET_KEY_ALGORITHM = "AES";
private static final String CIPHER_TRANSFORMER = "AES/CBC/PKCS5Padding";
private static Base64 base64Instance = new Base64();
public static String decrypt(String data, String password, String salt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8),
ITERATION_COUNT,
KEY_LENGTH);
SecretKey secretKey = factory.generateSecret(spec);
ByteBuffer buffer = ByteBuffer.wrap(base64Instance.decode(data));
buffer.position(DYN_SALT_LENGTH);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMER);
// Read the IV
byte[] ivBytes = new byte[cipher.getBlockSize()];
buffer.get(ivBytes, 0, ivBytes.length);
// Read encrypted text.
byte[] encryptedTextBytes = new byte[buffer.capacity() - DYN_SALT_LENGTH - ivBytes.length];
buffer.get(encryptedTextBytes);
// Initialize Cipher.
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), SECRET_KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
String result = new String(cipher.doFinal(encryptedTextBytes), StandardCharsets.UTF_8);
return result;
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt data", e);
}
}
public static String encrypt(String data, String password, String salt) {
// Create new salt for every new encryption request.
byte[] saltBytes = new byte[DYN_SALT_LENGTH];
new SecureRandom().nextBytes(saltBytes);
try {
// Create secret key spec.
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8),
ITERATION_COUNT,
KEY_LENGTH);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), SECRET_KEY_ALGORITHM);
byte[] ivBytes;
byte[] encryptedTextBytes;
// Initialize cipher
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMER);
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// Create initialization vector IV
ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
// Encrypt the text.
encryptedTextBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// Response will be in the form of <salt><IV><encryptedText>
ByteBuffer byteBuffer = ByteBuffer.allocate(saltBytes.length + ivBytes.length + encryptedTextBytes.length);
byteBuffer.put(saltBytes);
byteBuffer.put(ivBytes);
byteBuffer.put(encryptedTextBytes);
return base64Instance.encodeToString(byteBuffer.array());
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt data", e);
}
}
public static void main(String[] args) {
String password = "password";
String salt = "salt";
String data = "hello world";
String resultEncrypted = encrypt(data, password, salt);
System.out.println(resultEncrypted);
String resultDecrypted = decrypt(resultEncrypted, password, salt);
System.out.println(resultDecrypted);
}
}
I'm trying with JS code like below but without clue about what i'm doing wrong:
function getAlgorithm(keyBase64) {
var key = Buffer.from(keyBase64, "base64");
switch (key.length) {
case 16:
return "aes-128-cbc";
case 32:
return "aes-256-cbc";
}
throw new Error("Invalid key length: " + key.length);
}
function decrypt(messagebase64, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, "base64");
const iv = Buffer.from(ivBase64, "base64");
const decipher = crypto.createDecipheriv(
getAlgorithm(keyBase64),
key,
iv.slice(0, 16)
);
let decrypted = decipher.update(messagebase64, "base64", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
const base64Encrypted =
"2vSIh0J64zhrQuayUV+UIyPTpmSaN4gAv7B3CVC/a68eBfeU0bMwRm2I";
const key = crypto.scryptSync("password", "salt", 16);
const encrypted = Buffer.from(base64Encrypted, "base64");
const encryptedWOSalt = Buffer.from(base64Encrypted, "base64").slice(10);
const iv = encrypted.slice(10, 10 + 17);
const result = decrypt(
encryptedWOSalt.toString("base64"),
key,
iv.toString("base64")
);
console.log(result);
It's throwing an error:
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Thanks
Since you only posted a NodeJS decryption code, I focus on decryption. The encryption is to be implemented analogously. If you have problems with this, please post a new question with the corresponding encryption code.
There are several bugs in the NodeJS code:
Wrong key derivation (in the NodeJS code scrypt is used, while in the Java code PBKDF2/HMAC-SHA1 is applied).
Incorrect/missing separation of salt, IV and ciphertext
Encoding bugs and unnecessary encoding/decoding cycles
The following NodeJS code works:
var crypto = require('crypto')
function getAlgorithm(key) {
switch (key.length) {
case 16:
return "aes-128-cbc";
case 32:
return "aes-256-cbc";
}
throw new Error("Invalid key length: " + key.length);
}
function decrypt(message, key, iv) {
const decipher = crypto.createDecipheriv(
getAlgorithm(key),
key,
iv
);
let decrypted = Buffer.concat([decipher.update(message), decipher.final()]);
return decrypted.toString("utf8");
}
const DYN_SALT_LENGTH = 10;
const IV_LENGTH = 16;
const ITERATION_COUNT = 65556;
const KEY_LENGTH = 256;
const base64Encrypted = "ossqoyCaaQINWUkTsHNGRe5Isd5s7c7U8KcLua78Ehm9jAxQNOd2tyjj";
// Separate salt, IV and ciphertext
const encrypted = Buffer.from(base64Encrypted, "base64");
const salt = encrypted.slice(0, DYN_SALT_LENGTH);
const iv = encrypted.slice(DYN_SALT_LENGTH, DYN_SALT_LENGTH + IV_LENGTH);
const ciphertext = encrypted.slice(DYN_SALT_LENGTH + IV_LENGTH);
// Derive key voa PBKDF2/HMAC-SHA1
const key = crypto.pbkdf2Sync("password", "salt", ITERATION_COUNT, KEY_LENGTH/8, "sha1");
// Decrypt
const result = decrypt(
ciphertext,
key,
iv
);
console.log(result); // hello world
Note that the current Java code (and therefore also the NodeJS code) does not use the random salt for key derivation, but a static salt, possibly for testing purposes. In the final solution, the random salt is to be used for security reasons.
Is it normal that when decrypting AES-encoded encrypted text in CBC mode with an initialization vector different from the original, you get almost the original text?
I am attaching a complete example code, I did not create it but I took it from an online tutorial, I just modified the main to explain better and with an example what I mean:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class Main {
private static final String key = "aesEncryptionKey";
private static String initVector = "encryptionIntVec";
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String originalString = "password";
System.out.println("Original String to encrypt - " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted String with initVector: 'encryptionIntVec' - " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'encryptionIntVec' - " + decryptedString);
//output: "password"
initVector = "dncryftionIntVec";
String decryptedString2 = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'dncryftionIntVec' - " + decryptedString2);
//output: "qasswyrd"
}
}
Output:
Original String to encrypt - password
Encrypted String with initVector: 'encryptionIntVec' - AIDTAIiCazaQavILI07rtA==
Decrypted String with initVector: 'encryptionIntVec' - password
Decrypted String with initVector: 'dncryftionIntVec' - qasswyrd
To make it worse and to underline why #Maarten Bodewes wrote "CBC has limited so called error correction, which is one reason why it should not be preferred over authenticated encryption such as AES-GCM in most situations" see my example that is based on your code.
Think of an (encrypted) payment order that is sent in an email "Send 1000$ to Maarten and Artjom". The attacker gets
access to the initVector (as #Artjom B. wrote it is usually prepended to the ciphertext) and for AES it is 16 bytes long.
The attacker just guesses what the first 16 characters of the order are (because you use this string for every payment...)
and changes the initVector with these simple xor'ing (here I'm changing the initVector-string, in real I would change
the first 16 bytes of the message). Once again: the attacker has no access to the encryption key.
// here the attacker changes the initVector without knowledge of the encryptionKey
String guessedOrder = "Send 1000$ to Ma";
String newOrder = "Send 9876$ to Ma";
byte[] initvectorOrgByte = "encryptionIntVec".getBytes("UTF-8");
byte[] initvectorByte = new byte[initvectorOrgByte.length];
for (int i = 0; i < 16; i++) {
initvectorByte[i] = (byte) (initvectorOrgByte[i] ^ newOrder.getBytes("UTF-8")[i]
^ guessedOrder.getBytes("UTF-8")[i]);
}
initVector = new String(initvectorByte);
This is the result of decryption with the changed initVector:
Original String to encrypt - Send 1000$ to Maarten and Artjom
Encrypted String with initVector: 'encryptionIntVec' - 8raVjEwVYjYaKYcNihWD993Xv9KVxQQmD7xI5FYEx9JmhwxayT3mkIST1JogUkqC
Decrypted String with initVector: 'encryptionIntVec' - Send 1000$ to Maarten and Artjom
Decrypted String with initVector: 'encryx|ninIntVec' - Send 9876$ to Maarten and Artjom
So please do not use CBC-mode encryption and try to use GCM-mode or other authenticated modes where ever it is possible!
B.t.w.: this called "tampering".
My code:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
public class MainTampering {
private static final String key = "aesEncryptionKey";
private static String initVector = "encryptionIntVec";
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) throws UnsupportedEncodingException {
String originalString = "Send 1000$ to Maarten and Artjom";
System.out.println("Original String to encrypt - " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted String with initVector: 'encryptionIntVec' - " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'encryptionIntVec' - " + decryptedString);
//output: "Send 1000$ to Maarten"
// here the attacker changes the initVector without knowledge of the encryptionKey
String guessedOrder = "Send 1000$ to Ma";
String newOrder = "Send 9876$ to Ma";
byte[] initvectorOrgByte = "encryptionIntVec".getBytes("UTF-8");
byte[] initvectorByte = new byte[initvectorOrgByte.length];
for (int i = 0; i < 16; i++) {
initvectorByte[i] = (byte) (initvectorOrgByte[i] ^ newOrder.getBytes("UTF-8")[i]
^ guessedOrder.getBytes("UTF-8")[i]);
}
initVector = new String(initvectorByte);
//initVector = "encryptionIntVec";
String decryptedString2 = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: '" + initVector + "' - " + decryptedString2);
//output: "Send 9876$ to Maarten"
}
}
Yes. The ciphertext block is first block decrypted and then XOR'ed with the last ciphertext block or the IV if it is the first ciphertext block.
So if you look at the first character (characters in ASCII):
Difference in the init vector:
'e' ^ 'd' = 65h ^ 64h = 0110_0101b ^ 0110_0100b = 0000_0001b
Difference XOR'ed with the plaintext character:
'p' ^ 0000_0001b = 70h ^ 0000_0001b = 0111_0000b ^ 0000_0001b = 0111_0001b = 71h = 'q'
CBC has limited so called error propagation. Authenticated encryption such as AES-GCM should be preferred in most situations.
Note that CBC mode requires a unpredictable IV, which means basically that it should consist of (pseudo)random bytes.
Hi I have java code which decrypt the ciphertext encrypted using CryptoJS library(AES).
Now i wanted to write the javacode which will encrypt the plaintext again.
Please find the below code.
try {
String secret = "René Über";
String cipherText="U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.decode(cipherText, Base64.DEFAULT);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes("utf-8"), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData,"utf-8");
System.out.println("Decrypted "+decryptedText);
//Here I get right plain text as
//System.out: Decrypted The quick brown fox jumps over the lazy dog.
Cipher abc=Cipher.getInstance("AES/CBC/PKCS5Padding");
abc.init(Cipher.ENCRYPT_MODE,key,iv);
byte[] encryptedData=abc.doFinal(decryptedData);
String str=Base64.encodeToString(encryptedData,Base64.DEFAULT);
System.out.println("encrypted "+str);
//Here i want the encrypted text as
// encrypted U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hy//aQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
//but i receive
//System.out: encrypted IZ5IDQruC+Cz0pd5krBsIM0KzbM+j4FeO8pgusm60wr6HFPCX+HJpAs5oPssshGjYjl/J5Ew+//eui
}catch (Exception e)
{}
When I decrypt the code I get correct Plain Text but when I again encrypt the plain text I didnt get the encrypted text as previous.
Please Help.
GenerateKeyAndIV function code:-
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
Your ciphertext has "Salted__<8 byte salt>" at the beginning, which you skip when decrypting. You need to prefix the same in your encryption mode if you want to create OpenSSL compatible ciphertext.
Your encryption code ciphertext seems correct when you view it in a base64 to hex decoder, e.g. the one provided here. However, because each character only contains 64 bits and since the bytes have shifted 16 places (which is not divisible by 3), it just seams that your entire ciphertext is incorrect, while it is just missing 16 bytes at the front.
Here posting my working code for android I have used crypto for decryption on the server. Below code is using AES Algorithm
private static final String key = "aesExamplekey";
private static final String initVector = "exampleintvec";
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
// byte[] finalCiphertext = new byte[encrypted.length+2*16];
return Base64.encodeToString(encrypted, Base64.NO_WRAP);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}`
Server side code asp.net
public string DecryptStringAES(string cipherText)
{
// var keybytes = Encoding.UTF8.GetBytes("7061737323313233");
// var iv = Encoding.UTF8.GetBytes("7061737323313233");
var keybytes = Encoding.UTF8.GetBytes("aesExamplekey");
var iv = Encoding.UTF8.GetBytes("exampleintvec");
var encrypted = Convert.FromBase64String(cipherText);
var decriptedFromJavascript = DecryptStringFromBytes(encrypted, keybytes, iv);
return string.Format(decriptedFromJavascript);
}
[Edit:] SOLVED! See this article. Full working source code. JavaX.
[Original post:]
sorry to hit this fairly dead horse once more, but I can't get this to work... (I've provided a full example): http://tinybrain.de/1000344
Code:
import java.net.*;
import java.io.*;
import javax.swing.*;
import java.util.regex.*;
import java.util.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class main {
public static void main(String[] args) throws Exception {
byte[] data = "hello".getBytes("UTF-8");
printHex(data);
Random ranGen = new SecureRandom();
byte[] salt = new byte[8]; // 8 grains of salt
ranGen.nextBytes(salt);
String pw = "pw";
byte[] enc = encrypt(data, pw.toCharArray(), salt);
printHex(enc);
System.out.println("enc length: " + enc.length);
byte[] dec = decrypt(enc, pw.toCharArray(), salt);
System.out.println("decrypted: " + new String(dec, "UTF-8"));
}
static void printHex(byte[] data) {
System.out.println(bytesToHex(data));
}
static String bytesToHex(byte[] bytes) {
return bytesToHex(bytes, 0, bytes.length);
}
static String bytesToHex(byte[] bytes, int ofs, int len) {
StringBuilder stringBuilder = new StringBuilder(len*2);
for (int i = 0; i < len; i++) {
String s = "0" + Integer.toHexString(bytes[ofs+i]);
stringBuilder.append(s.substring(s.length()-2, s.length()));
}
return stringBuilder.toString();
}
static SecretKey makeKey(char[] password, byte[] salt) throws Exception {
/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// only with unlimited strength:
//KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
// Let's try this:
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
return secret;
}
public static byte[] encrypt(byte[] data, char[] password, byte[] salt) {
try {
SecretKey secret = makeKey(password, salt);
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(cipher.update(data));
baos.write(cipher.doFinal());
byte[] ciphertext = baos.toByteArray();
return ciphertext;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static byte[] decrypt(byte[] ciphertext, char[] password, byte[] salt) {
try {
SecretKey secret = makeKey(password, salt);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
/* Decrypt the message, given derived key and initialization vector. */
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
baos.write(cipher.update(ciphertext));
baos.write(cipher.doFinal());
return baos.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It says:
javax.crypto.BadPaddingException: Given final block not properly padded
at main.decrypt(main.java:98)
at main.main(main.java:26)
... 9 more
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2048)
at main.decrypt(main.java:95)
What to do?
You are using a random IV, but you are not sharing the random IV generated during encryption with the decryption method. Instead, it is using a random IV itself.
You've got:
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
but you don't seem to do anything with the IV afterwards.
I'm in the process of porting our Java application to OS X (10.8). One of our unit tests fails when doing encryption (it works on Windows). Both are running Java 7 Update 21 but the Windows version is using the 32 bit JDK and the Mac version is using the 64 bit JDK.
When running it on Mac I get the following exception when trying to decrypt the encrypted data:
Caused by: javax.crypto.BadPaddingException: Given final block not
properly padded at
com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811) at
com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676) at
com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087) at
com.degoo.backend.security.Crypto.processCipher(Crypto.java:56) ...
25 more
Here's the encryption class.
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public final class Crypto {
private final static String CIPHER_ALGORITHM = "AES";
private final static String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
public final static int CRYPTO_KEY_SIZE = 16;
public static byte[] encryptByteArray(byte[] blockToEncrypt, int maxLengthToEncrypt, byte[] encryptionKey, byte[] ivBytes) {
return processCipher(blockToEncrypt, maxLengthToEncrypt, Cipher.ENCRYPT_MODE, ivBytes, encryptionKey);
}
public static byte[] decryptByteArray(byte[] encryptedData, byte[] encryptionKey, byte[] ivBytes) {
return processCipher(encryptedData, encryptedData.length, Cipher.DECRYPT_MODE, ivBytes, encryptionKey);
}
private static byte[] processCipher(byte[] blockToEncrypt, int maxLength, int cryptionMode, byte[] ivBytes, byte[] encryptionKey) {
try {
IvParameterSpec iv = new IvParameterSpec(ivBytes);
final Cipher cipher = initCipher(cryptionMode, iv, encryptionKey);
return cipher.doFinal(blockToEncrypt, 0, maxLength);
} catch (Exception e) {
throw new RuntimeException("Failure", e);
}
}
private static Cipher initCipher(int cryptionMode, IvParameterSpec iv, byte[] encryptionKey) {
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance(CIPHER_ALGORITHM);
final SecureRandom randomSeed = new SecureRandom();
randomSeed.setSeed(encryptionKey);
keyGen.init(CRYPTO_KEY_SIZE * 8, randomSeed);
// Generate the secret key specs.
final SecretKey secretKey = keyGen.generateKey();
final SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), CIPHER_ALGORITHM);
// Instantiate the cipher
final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(cryptionMode, secretKeySpec, iv);
return cipher;
} catch (Exception e) {
throw new RuntimeException("Failure", e);
}
}
}
The test code looks like this:
public void testEncryption() throws Exception {
int dataLength = TestUtil.nextInt(applicationParameters.getDataBlockMinSize());
byte[] dataToEncrypt = new byte[dataLength];
TestUtil.nextBytes(dataToEncrypt);
int keyLength = 16;
byte[] key = new byte[keyLength];
TestUtil.nextBytes(key);
byte[] ivBytes = new byte[16];
TestUtil.nextBytes(key);
long startTime = System.nanoTime();
byte[] encryptedBlock = Crypto.encryptByteArray(dataToEncrypt, dataToEncrypt.length, key, ivBytes);
long endTime = System.nanoTime();
System.out.println("Encryption-speed: " + getMBPerSecond(dataLength, startTime, endTime));
startTime = System.nanoTime();
byte[] decryptedData = Crypto.decryptByteArray(encryptedBlock, key, ivBytes);
endTime = System.nanoTime();
System.out.println("Decryption-speed: " + getMBPerSecond(dataLength, startTime, endTime));
if (encryptedBlock.length == decryptedData.length) {
boolean isEqual = true;
//Test that the encrypted data is not equal to the decrypted data.
for (int i = 0; i < encryptedBlock.length; i++) {
if (encryptedBlock[i] != decryptedData[i]) {
isEqual = false;
break;
}
}
if (isEqual) {
throw new RuntimeException("Encrypted data is equal to decrypted data!");
}
}
Assert.assertArrayEquals(dataToEncrypt, decryptedData);
}
I think I've found it. For some reason the code above derives an encryption-key by seeding a SecureRandom instance with the existing encryption key to get a new byte[] (don't ask me why, it was a long time ago it was written). This is then fed to the SecretKeySpec constructor. If I skip all this and just feed the SecretKeySpec constructor the encryption key that we already have then the unit test passes. The code that does the encryption now looks like this:
final SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey, CIPHER_ALGORITHM);
// Instantiate the cipher
final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(cryptionMode, secretKeySpec, iv);
return cipher;
The odd thing is that it has worked on Windows. Looks like the SecureRandom implementations behave differently on OS X and on Windows. Calling setSeed on OS X appends to the seed whereas Windows replaces it.
Update: found some more details on the implementation differences of SecureRandom: http://www.cigital.com/justice-league-blog/2009/08/14/proper-use-of-javas-securerandom/