I have a PSKC XML file, that contains my Salt/Key, IV(Initialization
Vector), Encrypted Data, and password.
Now, I am trying to Do Password Based Encyption and Decryption using the PBEWITHSHA1AND128BITAES-CBC-BC Algorithm.
It works if I generate salt and IV randomly.
However, it fails if i don't generate it randomly. Instead of generating Salt and IV randomly, I am using my own salt, IV, password and everything.
Here is my code:
/**
* parts of this code were copied from the StandardPBEByteEncryptor class from
* the Jasypt (www.jasypt.org) project
*/
public class AESCrypt
{
private final String KEY_ALGORITHM = "PBEWITHSHA1AND128BITAES-CBC-BC";
// private final String KEY_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private final String MODE_PADDING = "/CBC/PKCS5Padding";
private final int DEFAULT_SALT_SIZE_BYTES = 128;
private byte[] ivParamSpec1 =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
private final SecureRandom rand;
private final String passwd = "kn4\"bE,W11kewsUd";
public AESCrypt() throws Exception
{
rand = SecureRandom.getInstance("SHA1PRNG");
}
private byte[] generateSalt(int size)
{
byte[] salt = new byte[size];
rand.nextBytes(salt);
return salt;
}
private SecretKey generateKey(String algorithm, int keySize, byte[] salt)
throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidKeySpecException
{
// SecretKeySpec Spec = new SecretKeySpec(keyBytes, algorithm);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwd.toCharArray(), salt,100000);
SecretKey tmpKey = factory.generateSecret(pbeKeySpec);
byte[] keyBytes = new byte[keySize / 8];
SecretKeySpec Spec = new SecretKeySpec(keyBytes, algorithm);
System.arraycopy(tmpKey.getEncoded(), 0, keyBytes, 0, keyBytes.length);
System.out.println("SecretKeySpec(keyBytes, algorithm)---->"+Spec);
return Spec;
}
private byte[] generateIV(Cipher cipher)
{
byte[] iv = new byte[cipher.getBlockSize()];
rand.nextBytes(iv);
return iv;
}
private byte[] appendArrays(byte[] firstArray, byte[] secondArray)
{
final byte[] result = new byte[firstArray.length + secondArray.length];
System.arraycopy(firstArray, 0, result, 0, firstArray.length);
System.arraycopy(secondArray, 0, result, firstArray.length, secondArray.length);
return result;
}
public byte[] encrypt(String algorithm, int keySize, final byte[] message)
throws Exception
{
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0)
{
saltSizeBytes = algorithmBlockSize;
}
// Create salt
// final byte[] salt = generateSalt(saltSizeBytes);
final byte[] salt = "EW0h0yUcDX72WU9UiKiCwDpXsJg=".getBytes();
SecretKey key = generateKey(algorithm, keySize, salt);
// create a new IV for each encryption
// final IvParameterSpec ivParamSpec = new IvParameterSpec();
ivParamSpec1 = "ixYgnjjY58RNacxZHwxgBQ==".getBytes();
final IvParameterSpec ivParamSpec = new IvParameterSpec(ivParamSpec1);
// Perform encryption using the Cipher
cipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
byte[] encryptedMessage = cipher.doFinal(message);
// append the IV and salt
encryptedMessage = appendArrays(ivParamSpec.getIV(), encryptedMessage);
encryptedMessage = appendArrays(salt, encryptedMessage);
return encryptedMessage;
}
public byte[] decrypt(String algorithm, int keySize, final byte[] encryptedMessage) throws Exception
{
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// determine the salt size for the first layer of encryption
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0)
{
saltSizeBytes = algorithmBlockSize;
}
System.out.println("saltSizeBytes:" + saltSizeBytes);
byte[] decryptedMessage = new byte[encryptedMessage.length];
System.arraycopy(encryptedMessage, 0, decryptedMessage, 0,
encryptedMessage.length);
// extract the salt and IV from the incoming message
byte[] salt = null;
byte[] iv = null;
byte[] encryptedMessageKernel = null;
final int saltStart = 0;
final int saltSize = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes
: decryptedMessage.length);
// final int saltSize = 32;
// System.out.println("saltSize:"+saltSize);
final int ivStart = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes
: decryptedMessage.length);
final int ivSize = cipher.getBlockSize();
final int encMesKernelStart = (saltSizeBytes + ivSize < decryptedMessage.length ? saltSizeBytes
+ ivSize
: decryptedMessage.length);
final int encMesKernelSize = (saltSizeBytes + ivSize < decryptedMessage.length ? (decryptedMessage.length
- saltSizeBytes - ivSize)
: 0);
salt = new byte[saltSize];
iv = new byte[ivSize];
System.out.println("saltSize:" + saltSize);
System.out.println("ivSize:" + ivSize);
encryptedMessageKernel = new byte[encMesKernelSize];
System.out.println("encryptedMessageKernel");
System.arraycopy(decryptedMessage, saltStart, salt, 0, saltSize);
System.arraycopy(decryptedMessage, ivStart, iv, 0, ivSize);
System.arraycopy(decryptedMessage, encMesKernelStart,
encryptedMessageKernel, 0, encMesKernelSize);
SecretKey key = generateKey(algorithm, keySize, salt);
System.out.println("ekey");
ivParamSpec1 = "ixYgnjjY58RNacxZHwxgBQ==".getBytes();
IvParameterSpec ivParamSpec = new IvParameterSpec(ivParamSpec1);
// Perform decryption using the Cipher
cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec);
decryptedMessage = cipher.doFinal(encryptedMessageKernel);
// Return the results
return decryptedMessage;
}
private byte[] decryptWithLWCrypto(byte[] cipher, String password, byte[] salt, final int iterationCount)
throws Exception
{
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, salt, iterationCount);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
aesCBC.init(false, aesCBCParams);
PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC,new PKCS7Padding());
byte[] plainTemp = new byte[aesCipher.getOutputSize(cipher.length)];
int offset = aesCipher.processBytes(cipher, 0, cipher.length, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
System.out.println("Plain--->"+plain);
return plain;
}
public static void main(String[] args) throws Exception
{
Security.addProvider(new BouncyCastleProvider());
AESCrypt aesCrypt = new AESCrypt();
String originalText = "6skYngWZxkTgsRIoFPLS1mpI32Q=";
String toDecrypt = new String(Base64.encode(aesCrypt.encrypt("AES", 128, originalText.getBytes())));
System.out.println("Original Data----->"+originalText);
System.out.println("After Encrpytion--->"+toDecrypt);
byte[] criptata = Base64.decode(toDecrypt);
byte[] decriptata = aesCrypt.decrypt("AES", 128, criptata);
String msgdecriptato = new String(decriptata);
System.out.println("After Decrption--->"+msgdecriptato);
if (!originalText.equals(msgdecriptato))
{
throw new IllegalStateException("Strings do not match!");
}
else
{
System.out.println("Matched.....");
}
// byte[] cipher = "RVcwaDB5VWNEWDcyV1U5VWlLaUN3RHBYc0pnPWl4WWduampZNThSTmFjeFoJfgJbigcnDs0TJdNu7yEkprlJFdilLrLJT8uoQ3dm/A==".getBytes();
// String password = "kn4\"bE,W11kewsUd";
// byte[] salt = "EW0h0yUcDX72WU9UiKiCwDpXsJg=".getBytes();
// System.out.println(aesCrypt.decryptWithLWCrypto(cipher, password, salt , 128));
}
}
When I try to run this... Am getting following Errors,
Exception in thread "main" java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
at com.sun.crypto.provider.SunJCE_f.a(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineInit(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
at AESCrypt.encrypt(AESCrypt.java:113)
at AESCrypt.main(AESCrypt.java:213)
I can't truncate the salt size or IV size, then there will not be any data integrity.
Even if i truncate my IV size to 16 bytes from 28 bytes, it works for encryption, but it doesn't work for decryption. It produces the following errors:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at AESCrypt.decrypt(AESCrypt.java:163)
at AESCrypt.main(AESCrypt.java:178)
Can anyone help me?
Related
I'm trying to Encrypt and decrypt a string in java using the following code:
public byte[] encrypt(String message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("md5");
final byte[] digestOfPassword = md.digest("MYKEY12345"
.getBytes("utf-8"));
final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
// final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
final Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
final byte[] plainTextBytes = message.getBytes("utf-8");
final byte[] cipherText = cipher.doFinal(plainTextBytes);
// final String encodedCipherText = new sun.misc.BASE64Encoder()
// .encode(cipherText);
Log.d("base64", Base64.getEncoder().encodeToString(cipherText));
return cipherText;
}
public String decrypt(byte[] message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("md5");
final byte[] digestOfPassword = md.digest("MYKEY12345"
.getBytes("utf-8"));
final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
// final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
final Cipher decipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
decipher.init(Cipher.DECRYPT_MODE, key);
// final byte[] encData = new
// sun.misc.BASE64Decoder().decodeBuffer(message);
final byte[] plainText = decipher.doFinal(message);
return new String(plainText, "UTF-8");
}
usage:
byte[] codedtext = new Common().encrypt("HELLOWORLD!"); // Function to get the Encrption
Output:
base64: Ya9zBTukyOmdOh5/5vCaGA== // encrypted string converted to base64
Encrypted : [B#d41c149
ToDecrypt:
String decodedtext = new Common().decrypt(codedtext); // To decrypt String
Output:
Decrypted : HELLOWORLD! // from Encrypted string
Now if i use the same Key and String to get the encryption key online i get different values.
Using this link to verify my encryption/decryption.
I'm just starting in encryption/decryption so any help is appreciated about any thing that i'm doing wrong here.
You need
final byte[] plainText = decipher.doFinal(Base64.getDecoder().decode(message));
I'm doing an integration with another system and the data given is encrypted in AES-256-CBC(Java) and need to decrypt it in NodeJs in order to proceed.
I have tried many ways from internet and stuck in error. Below is the sample code of Java(decryption) which is working and NodeJs(my code of decryption)
private static final int ITERATION_COUNT = 65536;
private static final int KEY_LENGTH = 256;
private static final byte[] DEFAULT_IV = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
public static byte[] decryptToBytes(String src, String secret, String salt, byte[] iv) {
try{
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
return cipher.doFinal(Base64.getDecoder().decode(src));
}catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String src, String secret, String salt, byte[] iv) {
try{
return new String(decryptToBytes(src, secret, salt, iv));
}catch (Exception ex) {
return null;
}
}
public static void main(String[] args) {
String secret = "abcd123456";
String salt = "123abc";
String plainText ="This is AES256 encryption test";
String cipherText = "gbYgtu5EWxOYRSUmMsEtdn8oQLxBjejfwUBSRhhls08=";
byte[] IV = new byte[16];
String originalText = decrypt(cipherText,secret, salt, IV);
}
import crypto from "crypto";
public aesCdcDecrypt(input: string) {
let iterationCount = 65536;
let keyLength = 256;
let iv = new Buffer(16);
let keyHex = "abcd123456";
let salt = "123abc";
let decryptText: string;
try {
crypto.pbkdf2(new Buffer(keyHex), new Buffer(salt), iterationCount, keyLength, "sha256", function (err, key) {
let secretKey = key.toString("hex");
let decipher = crypto.createDecipheriv("aes-256-cbc", secretKey, iv);
decryptText = decipher.update(input, "binary", "utf8");
decryptText += decipher.final("utf8");
console.log('Result: ' + decryptText);
});
} catch (e) {
console.log(e);
}
return decryptText;
}
Result getting this error -->
Error: Invalid key length
at new Decipheriv (crypto.js:267:16)
at Object.createDecipheriv (crypto.js:627:10)
There are a few minor issues in your TS code:
key length is in bytes, not bits
new Buffer() does not decode base64 by default
Here's a working version (JS):
const crypto = require('crypto')
function aesCdcDecrypt(ciphertext) {
let iterationCount = 65536;
let keyLength = 32;
let iv = Buffer.alloc(16);
let keyHex = "abcd123456";
let salt = "123abc";
let key = crypto.pbkdf2Sync(keyHex, Buffer.from(salt), iterationCount, keyLength, "sha256");
var cipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
cipher.setAutoPadding(true);
let ciph = cipher.update(Buffer.from(ciphertext, "base64"));
let ciphf = cipher.final();
return Buffer.concat([ciph, ciphf]).toString();
}
console.log(aesCdcDecrypt("gbYgtu5EWxOYRSUmMsEtdn8oQLxBjejfwUBSRhhls08="));
Prints:
This is AES256 encryption test
I'm attempting to convert this C# encryption algorithm to Java; however, I keep retrieving slightly different encrypted results (haven't tried decryption yet). It may also be important to point out that I'm not able to change the C# code.
However when I call the encrypt function in C# on the string "test" it will return nmj8MjjO52y928Syqf0J+g== However in Java it'll return C6xyQjJCqVo=
The C#
private static String key = "012345678901234567890123";
public static string encrypt(String stringToEncrypt)
{
TripleDES des = CreateDES(key);
ICryptoTransform ct = des.CreateEncryptor();
byte[] input = Encoding.Unicode.GetBytes(stringToEncrypt);
byte[] output = ct.TransformFinalBlock(input, 0, input.Length);
//return output;
return Convert.ToBase64String(output);
}
public static String decrypt(string encryptedString)
{
byte[] input = Convert.FromBase64String(encryptedString);
TripleDES des = CreateDES(key);
ICryptoTransform ct = des.CreateDecryptor();
byte[] output = ct.TransformFinalBlock(input, 0, input.Length);
return Encoding.Unicode.GetString(output);
}
public static TripleDES CreateDES(string key)
{
MD5 md5 = new MD5CryptoServiceProvider();
TripleDES des = new TripleDESCryptoServiceProvider();
des.Key = md5.ComputeHash(Encoding.Unicode.GetBytes(key));
des.IV = new byte[des.BlockSize / 8];
return des;
}
My Attempt with converting to Java
private static String key = "012345678901234567890123";
public static void main(String[] args) throws Exception {
String text = "test";
String codedtext = encrypt(text);
//String decodedtext = decrypt(codedtext);
System.out.println(new String(codedtext));
//System.out.println(decodedtext);
}
public static String encrypt(String message) throws Exception {
MessageDigest md = MessageDigest.getInstance("md5");
byte[] digestOfPassword = md.digest(key.getBytes("unicode"));
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
//for (int j = 0, k = 16; j < 8;) {
// keyBytes[k++] = keyBytes[j++];
//}
SecretKey key = new SecretKeySpec(keyBytes, "DESede");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] plainTextBytes = message.getBytes();
byte[] cipherText = cipher.doFinal(plainTextBytes);
String output = Base64.encode(cipherText);
return output;
}
public static String decrypt(String message) throws Exception {
byte[] messageBytes = Base64.decode(message);
MessageDigest md = MessageDigest.getInstance("md5");
byte[] digestOfPassword = md.digest(key.getBytes());
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
SecretKey key = new SecretKeySpec(keyBytes, "DESede");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher decipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
decipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] plainText = decipher.doFinal(messageBytes);
return new String(plainText);
}
Does anyone see what I'm overseeing?
You are missing two things. You are using a 16 length key on the c# side since it is not padded like the Java version. By default if the key is 16 bytes in length it will be padded with the first 8 bytes of the key.
To make this match on the Java side you will have to uncomment that line that adds the padding to the key:
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
SecretKey secretKey = new SecretKeySpec(keyBytes, 0, 24, "DESede");
Also, on the java side there was a suggestion to make sure to use UTF-LE for the text. Make sure to use it for everything. So the lines:
byte[] digestOfPassword = md.digest(key.getBytes("UTF-16LE"));
byte[] plainTextBytes = clearText.getBytes("UTF-16LE");
In general I would make sure to set the c# params of all the tripledes object, and not depend on defaults.
Here are two versions that match in c# and java
Java
String key = "012345678901234567890123";
String clearText = "test";
MessageDigest md = MessageDigest.getInstance("md5");
byte[] digestOfPassword = md.digest(key.getBytes("UTF-16LE"));
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
String byteText = Arrays.toString(keyBytes);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
SecretKey secretKey = new SecretKeySpec(keyBytes, 0, 24, "DESede");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
byte[] plainTextBytes = clearText.getBytes("UTF-16LE");
byte[] cipherText = cipher.doFinal(plainTextBytes);
String output = Base64.encode(cipherText);
c#
string clearText = "test";
string key = "012345678901234567890123";
string encryptedText = "";
MD5 md5 = new MD5CryptoServiceProvider();
TripleDES des = new TripleDESCryptoServiceProvider();
des.KeySize = 128;
des.Mode = CipherMode.CBC;
des.Padding = PaddingMode.PKCS7;
byte[] md5Bytes = md5.ComputeHash(Encoding.Unicode.GetBytes(key));
byte[] ivBytes = new byte[8];
des.Key = md5Bytes;
des.IV = ivBytes;
byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
ICryptoTransform ct = des.CreateEncryptor();
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
}
encryptedText = Convert.ToBase64String(ms.ToArray());
}
Edited: Both versions now return the test case result "nmj8MjjO52y928Syqf0J+g=="
I've been having trouble adding a MAC to my password-based AES encryption/decryption program. I am trying to add the MAC'd plaintext and salt (to be used with password) (both byte arrays) to a final array along with the ciphertext, and then decrypt by reading in the ciphertext file and splitting it back up into salt, MAC, and cipher text byte arrays.
The encryption class seems to be running smoothly but the decryption class does not. I debugged the the class and found that it fails because it never enters the if statement that checks whether the computed and recovered MACs are the same:
if(Arrays.equals(macBytes, hmac))
I couldn't figure out why until I printed out the byte arrays for the salt, message, and MAC, and found that they don't match when printed from the encryption and decryption classes. All the array sizes match up across the two classes, but the byte values change somewhere.
Both classes worked perfectly without the MAC before, but I didn't add the salt directly to the encrypted data then and instead wrote it to a separate file. Including it with the encrypted data makes this slightly more portable for me, but was it a bad choice to do so? Is it better to write it to a separate file? Or am I just missing something blatantly obvious in my code?
Here is the full code.
Encryption class
public class AESEncryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
private final String PLAINTEXT = "/Volumes/CONNOR P/Unencrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private static final int SALT_SIZE = 64;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
public AESEncryption()
{
try
{
encrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void encrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int encryptedSize = (int)encrypted.length();
int plaintextSize = (int)plaintext.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(PLAINTEXT));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(ENCRYPTED));
//Create salt
byte[] salt = new byte[SALT_SIZE];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(salt);
//Create cipher key
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKey secret = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), ALGORITHM);
//Create cipher
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
//Read plaintext file into byte array
byte[] input = new byte[encryptedSize];
Path path = Paths.get(PLAINTEXT);
input = Files.readAllBytes(path);
byte[] crypt = cipher.doFinal(input);
//Create MAC object and apply to the byte array crypt[] containing the plaintext
KeyGenerator keyGenerator = KeyGenerator.getInstance(MAC_ALGORITHM);
SecretKey macKey = keyGenerator.generateKey();
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] macBytes = mac.doFinal(crypt);
//Add salt, MAC'd plaintext, and encrypted plaintext to final array
byte[] output = new byte[SALT_SIZE + crypt.length + macBytes.length];
System.arraycopy(salt, 0, output, 0, SALT_SIZE);
System.arraycopy(macBytes, 0, output, SALT_SIZE, macBytes.length);
System.arraycopy(crypt, 0, output, SALT_SIZE + macBytes.length, crypt.length);
//Write array with encrypted data to a new file
bufferedOutputStream.write(output);
bufferedInputStream.close();
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
Decryption class
public class AESDecryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
private final String PLAINTEXT = "/Volumes/CONNOR P/De-Encrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private final int SALT_SIZE = 64;
private final int IV_SIZE = 16;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
public AESDecryption()
{
try
{
decrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void decrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int encryptedSize = (int)encrypted.length();
int plaintextSize = (int)plaintext.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(encrypted));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(plaintext));
//Read in the encrypted data
byte[] input = new byte[encryptedSize];
Path path = Paths.get(ENCRYPTED);
input = Files.readAllBytes(path);
int increment = (input.length-SALT_SIZE)/2;
if(input.length >= (SALT_SIZE + increment))
{
//Recover salt, MAC, and encrypted data and store in arrays
byte[] salt = Arrays.copyOfRange(input, 0, SALT_SIZE);
byte[] macBytes = Arrays.copyOfRange(input, SALT_SIZE, SALT_SIZE + increment);
byte[] crypt = Arrays.copyOfRange(input, SALT_SIZE + increment, input.length);
//Regenerate original MAC
KeyGenerator keyGenerator = KeyGenerator.getInstance(MAC_ALGORITHM);
SecretKey macKey = keyGenerator.generateKey();
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] hmac = mac.doFinal(crypt);
if(Arrays.equals(macBytes, hmac)) //This is where it fails, never enters
{
//Regenerate cipher and decrypt data
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKey secret = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
//Write decrypted data to new text file
byte[] output = cipher.doFinal(crypt);
bufferedOutputStream.write(output);
bufferedInputStream.close();
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
}
Thanks for any help. It is much appreciated.
public class AESEncryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String PRNG_ALGORITHM = "SHA1PRNG";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String PLAINTEXT = "/Volumes/CONNOR P/Unencrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String IV_FILE_NAME = "iv.enc";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private final int SALT_SIZE = 16;
private final int IV_SIZE = 16;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
private final int START = 0;
public AESEncryption()
{
try
{
encrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void encrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int plaintextSize = (int)plaintext.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(plaintext));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(encrypted));
//Create salt for cipher key
byte[] salt = new byte[SALT_SIZE];
SecureRandom saltSecureRandom = SecureRandom.getInstance(PRNG_ALGORITHM);
saltSecureRandom.nextBytes(salt);
//Create cipher key & use to initialize cipher
byte[] keyBytes = PBEKeyFactory.getKey(PASSWORD, salt, ITERATIONS, KEY_LENGTH);
SecretKeySpec secret = new SecretKeySpec(keyBytes, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
//Create byte array of encrypted data
byte[] input = new byte[plaintextSize];
Path path = Paths.get(PLAINTEXT);
input = Files.readAllBytes(path);
byte[] crypt = cipher.doFinal(input);
//Create salt for the MAC key for added security
byte[] macsalt = new byte[SALT_SIZE];
SecureRandom macsaltSecureRandom = SecureRandom.getInstance(PRNG_ALGORITHM);
macsaltSecureRandom.nextBytes(macsalt);
//PBEKeyFactory.getKey(password, salt, iterations, keylength)
//returns a byte array representation of a SecretKey.
//Used a SecretKeyFactory instead of a KeyGenerator to make key.
//SecretKeyFactory gives back the same key given the same specifications
//whereas KeyGenerator gives back a new random key each time.
byte[] macPBE = PBEKeyFactory.getKey(PASSWORD, macsalt, ITERATIONS, KEY_LENGTH);
SecretKeySpec macKey = new SecretKeySpec(macPBE, MAC_ALGORITHM);
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] macBytes = mac.doFinal(crypt);
byte[] output = new byte[SALT_SIZE + SALT_SIZE + crypt.length + macBytes.length];
System.arraycopy(salt, START, output, START, SALT_SIZE);
System.arraycopy(macsalt, START, output, SALT_SIZE, SALT_SIZE);
System.arraycopy(macBytes, START, output, SALT_SIZE + SALT_SIZE, macBytes.length);
System.arraycopy(crypt, START, output, SALT_SIZE + SALT_SIZE + macBytes.length, crypt.length);
bufferedInputStream.close();
bufferedOutputStream.write(output);
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
public class AESDecryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String PLAINTEXT = "/Volumes/CONNOR P/De-Encrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private final int SALT_SIZE = 16;
//MAC key size is 256 bits (32 bytes) since it is created with
//the HmacSHA256 algorithm
private final int MAC_SIZE = 32;
private final int IV_SIZE = 16;
private final int START = 0;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
public AESDecryption()
{
try
{
decrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void decrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int encryptedSize = (int)encrypted.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(encrypted));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(plaintext));
//Read in encrypted data
byte[] input = new byte[encryptedSize];
Path path = Paths.get(ENCRYPTED);
input = Files.readAllBytes(path);
if(input.length >= (SALT_SIZE*2 + MAC_SIZE))
{
byte[] cryptSalt = Arrays.copyOfRange(input, START, SALT_SIZE);
byte[] macSalt = Arrays.copyOfRange(input, SALT_SIZE, SALT_SIZE*2);
byte[] macBytes = Arrays.copyOfRange(input, SALT_SIZE*2, (SALT_SIZE*2 + MAC_SIZE));
byte[] cryptBytes = Arrays.copyOfRange(input, (SALT_SIZE*2 + MAC_SIZE), input.length);
//This generates the same MAC key from encryption.
//Before, the KeyGenerator created a new random key
//meaning the derived and computed MAC keys were never the same
byte[] macKeyBytes = PBEKeyFactory.getKey(PASSWORD, macSalt, ITERATIONS, KEY_LENGTH);
SecretKeySpec macKey = new SecretKeySpec(macKeyBytes, MAC_ALGORITHM);
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] compMacBytes = mac.doFinal(cryptBytes);
//Check if computed and derived MAC's are the same
if(Arrays.equals(macBytes, compMacBytes))
{
//Creates same key from encryption
byte[] cryptKeyBytes = PBEKeyFactory.getKey(PASSWORD, cryptSalt, ITERATIONS, KEY_LENGTH);
SecretKeySpec cryptKey = new SecretKeySpec(cryptKeyBytes, ALGORITHM);
//Creates cipher and reads decrypted data to array
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, cryptKey, new IvParameterSpec(new byte[16]));
byte[] output = cipher.doFinal(cryptBytes);
bufferedInputStream.close();
bufferedOutputStream.write(output);
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
}
}
//This class has only one method, getKey(), which returns a byte array
//of a SecretKey of the corresponding parameters
public class PBEKeyFactory
{
private static final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
public static byte[] getKey(String password, byte[] salt, int iterations, int length) throws Exception
{
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, length);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
return factory.generateSecret(keySpec).getEncoded();
}
}
I'm trying to support PBE for AES, Serpent, and TwoFish. Currently I am able to generate an AES PBEKey in Java using BC like this:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC", provider);
PBEKeySpec pbeKeySpec = new PBEKeySpec("Password12".toCharArray());
SecretKey key = factory.generateSecret(pbeKeySpec);
but I can't figure out how to generate a PBEKey for Serpent, so I'm assuming its not possible out of the box. How would I go about implementing this? Is there a hook somewhere that I can just register my own SecretKeyFactory to handle Serpent keys?
Coincidentally, I have noticed that using an AES PBEKey (as generated above) for encrypting/decrypting with Serpent/TwoFish "works", but I have no idea what the repercussions are. Could I just get away with using the AES PBEKey?
After discussions with PaĆloEbermann (above), I put together the following solution. It generates a PBE key for AES256 and then simply copies the required number of bytes from the generated key into a new SecretKeySpec(), which allows me to specify the desired algorithm and key length. Currently I am salting the password AND creating a random IV on each call to encrypt. My assumption is that the IV is unnecessary since a random salt is applied to each encrypted message, but I wasn't 100% sure so I added the IV anyway. I'm hoping someone can confirm or deny this assumption, since if the IV isnt needed, then its bloating the size of the output from encrypt() for no valid reason. Ideally I would be able to generate a PBEKey of variable length with no algorithm ties (as per PKCS5), but it appears I am bound to the key sizes defined in the available PBE schemes provided by the selected Provider. This implementation is therefore bound to using BouncyCastle, since I was unable to find a PBE scheme that provided at least 256bit keys from the standard JCE provider.
/**
* parts of this code were copied from the StandardPBEByteEncryptor class from the Jasypt (www.jasypt.org) project
*/
public class PBESample {
private final String KEY_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private final String MODE_PADDING = "/CBC/PKCS5Padding";
private final int DEFAULT_SALT_SIZE_BYTES = 16;
private final SecureRandom rand;
private final String passwd = "(Password){12}<.....>!";
public PBESample() throws Exception {
rand = SecureRandom.getInstance("SHA1PRNG");
}
private byte[] generateSalt(int size) {
byte[] salt = new byte[size];
rand.nextBytes(salt);
return salt;
}
private SecretKey generateKey(String algorithm, int keySize, byte[] salt) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwd.toCharArray(), salt, 100000);
SecretKey tmpKey = factory.generateSecret(pbeKeySpec);
byte[] keyBytes = new byte[keySize / 8];
System.arraycopy(tmpKey.getEncoded(), 0, keyBytes, 0, keyBytes.length);
return new SecretKeySpec(keyBytes, algorithm);
}
private byte[] generateIV(Cipher cipher) {
byte[] iv = new byte[cipher.getBlockSize()];
rand.nextBytes(iv);
return iv;
}
private byte[] appendArrays(byte[] firstArray, byte[] secondArray) {
final byte[] result = new byte[firstArray.length + secondArray.length];
System.arraycopy(firstArray, 0, result, 0, firstArray.length);
System.arraycopy(secondArray, 0, result, firstArray.length, secondArray.length);
return result;
}
public byte[] encrypt(String algorithm, int keySize, final byte[] message) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
// Create salt
final byte[] salt = generateSalt(saltSizeBytes);
SecretKey key = generateKey(algorithm, keySize, salt);
// create a new IV for each encryption
final IvParameterSpec ivParamSpec = new IvParameterSpec(generateIV(cipher));
// Perform encryption using the Cipher
cipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
byte[] encryptedMessage = cipher.doFinal(message);
// append the IV and salt
encryptedMessage = appendArrays(ivParamSpec.getIV(), encryptedMessage);
encryptedMessage = appendArrays(salt, encryptedMessage);
return encryptedMessage;
}
public byte[] decrypt(String algorithm, int keySize, final byte[] encryptedMessage) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// determine the salt size for the first layer of encryption
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
byte[] decryptedMessage = new byte[encryptedMessage.length];
System.arraycopy(encryptedMessage, 0, decryptedMessage, 0, encryptedMessage.length);
// extract the salt and IV from the incoming message
byte[] salt = null;
byte[] iv = null;
byte[] encryptedMessageKernel = null;
final int saltStart = 0;
final int saltSize = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
final int ivStart = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
final int ivSize = cipher.getBlockSize();
final int encMesKernelStart = (saltSizeBytes + ivSize < decryptedMessage.length ? saltSizeBytes + ivSize : decryptedMessage.length);
final int encMesKernelSize = (saltSizeBytes + ivSize < decryptedMessage.length ? (decryptedMessage.length - saltSizeBytes - ivSize) : 0);
salt = new byte[saltSize];
iv = new byte[ivSize];
encryptedMessageKernel = new byte[encMesKernelSize];
System.arraycopy(decryptedMessage, saltStart, salt, 0, saltSize);
System.arraycopy(decryptedMessage, ivStart, iv, 0, ivSize);
System.arraycopy(decryptedMessage, encMesKernelStart, encryptedMessageKernel, 0, encMesKernelSize);
SecretKey key = generateKey(algorithm, keySize, salt);
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
// Perform decryption using the Cipher
cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec);
decryptedMessage = cipher.doFinal(encryptedMessageKernel);
// Return the results
return decryptedMessage;
}
public static void main(String[] args) throws Exception {
// allow the use of the BC JCE
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
final String message = "Secret Message";
PBESample engine = new PBESample();
byte[] encryptedMessage = engine.encrypt("AES", 128, message.getBytes());
byte[] decryptedMessage = engine.decrypt("AES", 128, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("AES OK");
}
encryptedMessage = engine.encrypt("Serpent", 256, message.getBytes());
decryptedMessage = engine.decrypt("Serpent", 256, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("Serpent OK");
}
encryptedMessage = engine.encrypt("TwoFish", 256, message.getBytes());
decryptedMessage = engine.decrypt("TwoFish", 256, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("TwoFish OK");
}
}
}