Reputation: 3
I have the following code to encrypt-decrypt a string using a key and random IV. However during decrypt I get a lot of zeros at the end in my IDE.
public class Example {
private static final String AES_MODE = "AES/CBC/PKCS5Padding";
private static final String CHARSET = "UTF-8";
private static final String HASH_ALGORITHM = "SHA-256";
private static final String KEY = "SUPER_SECURE_KEY";
private static SecretKeySpec getSecretKey() throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
byte[] bytes = KEY.getBytes(CHARSET);
digest.update(bytes, 0, bytes.length);
byte[] key = digest.digest();
return new SecretKeySpec(key, "AES");
}
public static String encrypt(String message) {
if(message == null || message.isEmpty()) {
return "";
}
try {
final SecretKeySpec key = getSecretKey();
byte[] cipherText = encrypt(key, message.getBytes(CHARSET));
return Base64.getEncoder().encodeToString(cipherText);
} catch (Exception e) {
System.out.print(e.toString());
return "";
}
}
private static byte[] encrypt(final SecretKeySpec key, final byte[] message) throws GeneralSecurityException {
final Cipher cipher = Cipher.getInstance(AES_MODE);
byte[] iv = new byte[cipher.getBlockSize()];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = new byte[iv.length + cipher.getOutputSize(message.length)];
System.arraycopy(iv, 0, ciphertext, 0, iv.length);
cipher.doFinal(message, 0, message.length, ciphertext, iv.length);
return ciphertext;
}
// ========================================================================================
public static String decrypt(String base64EncodedCipherText) {
if(base64EncodedCipherText == null || base64EncodedCipherText.isEmpty()) {
return "";
}
try {
final SecretKeySpec key = getSecretKey();
byte[] decodedCipherText = Base64.getDecoder().decode(base64EncodedCipherText);
byte[] decryptedBytes = decrypt(key, decodedCipherText);
return new String(decryptedBytes, CHARSET);
} catch (Exception e) {
System.out.print(e.toString());
return "";
}
}
private static byte[] decrypt(final SecretKeySpec key, final byte[] decodedCipherText) throws GeneralSecurityException {
final Cipher cipher = Cipher.getInstance(AES_MODE);
IvParameterSpec ivSpec = new IvParameterSpec(decodedCipherText, 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
int plainTextLength = decodedCipherText.length - cipher.getBlockSize();
byte[] plaintext = new byte[plainTextLength];
cipher.doFinal(decodedCipherText, cipher.getBlockSize(), plainTextLength, plaintext, 0);
return plaintext;
// return cipher.doFinal(decodedCipherText);
}
// ========================================================================================
public static void main(String[] args) {
String message = "Message to encrypt.";
String encryptedText = encrypt(message);
System.out.println(encryptedText);
String decryptedText= decrypt(encryptedText);
System.out.println(decryptedText);
}
}
The output I get in IntelliJ IDEA is: here
I think I am correctly separating the IV from the ciphertext, and decrypt the ciphertext with the key and the random IV. But still end up getting zeros in the end. Any pointers to what is wrong?
Upvotes: 0
Views: 655
Reputation: 38771
CBC mode as normally used requires padding, which your code correctly specifies, so the ciphertext (before adding and after removing the IV) is longer than the plaintext. You allocate a buffer for this longer size and Cipher.doFinal
only stores the actual plaintext to it, leaving the remaining bytes with the value initialized by new byte[n]
which is (always) zero.
You could determine the size the output will be using This doesn't work; Maarten is right.ciper.getOutputSize(int)
much as you did for encrypt
You could continue to overallocate the output buffer, but save the return value from cipher.doFinal (input,off,len, output,off)
which is an int
that tells you the number of bytes output (decrypted), and then use only that many bytes from the buffer e.g. new String (output, 0, outlen, charset)
or Arrays.copyOf(output, outlen)
But the easiest way is to use the doFinal
overload that allocates the buffer itself (with the correct size) and returns it:
return cipher.doFinal(decodedCipherText, cipher.getBlockSize(), decodedCipherText.length - cipher.getBlockSize());
Concur with not using a simple hash on a password, but your example doesn't show or say if your 'key' is really a password (handled by humans, and needing 'stretching') or just a text form of something with adequate entropy, for which a simple hash is okay.
Upvotes: 0
Reputation: 102775
Reading is fundamental. The docs for getOutputSize
indicate you can't use it for this purpose:
The actual output length of the next update or doFinal call may be smaller than the length returned by this method.
Encrypt it then check the resulting byte array, or do something with the return value of the doFinal
method (which really tells you how many bytes it made), or make a ByteArrayOutputStream
and send both the iv and the bytes from doFinal
(taking into account what it returns) there, then ask it for the byte[]
, or use a ByteBuffer.
Note that CBC is dubious, as is pass hashing with SHA-256. It works, but it's 'too fast', it's very easy for a hacker to try a few billion passwords a second. In general you shouldn't be handrolling this stuff.
Upvotes: 1