I am working on a text encryption and decryption project (following Struts 2)
Whenever I enter the password and the plain text I get a Invalid AES Key Length error.
The Service Class
package com.anoncrypt.services;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class SymAES
{
private static final String ALGORITHM = "AES";
private static byte[] keyValue= new byte[] { 'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' };
public String encode(String valueToEnc) throws Exception {
Key key = new SecretKeySpec(keyValue, ALGORITHM);
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encValue = c.doFinal(valueToEnc.getBytes());
String encryptedValue = new BASE64Encoder().encode(encValue);
return encryptedValue;
}
public String decode(String encryptedValue) throws Exception {
Key key = new SecretKeySpec(keyValue, ALGORITHM);
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedValue);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
public void start(String passcode)throws Exception
{
keyValue = passcode.getBytes();
}
}
And this is the error
java.security.InvalidKeyException: Invalid AES key length: 6 bytes
com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
com.sun.crypto.provider.ElectronicCodeBook.init(ElectronicCodeBook.java:93)
com.sun.crypto.provider.CipherCore.init(CipherCore.java:582)
com.sun.crypto.provider.CipherCore.init(CipherCore.java:458)
com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:307)
javax.crypto.Cipher.implInit(Cipher.java:797)
javax.crypto.Cipher.chooseProvider(Cipher.java:859)
javax.crypto.Cipher.init(Cipher.java:1229)
javax.crypto.Cipher.init(Cipher.java:1166)
com.anoncrypt.services.SymAES.encode(SymAES.java:35)
com.anoncrypt.actions.SymEncrypt.execute(SymEncrypt.java:24)
Things to know in general:
Key != Password
SecretKeySpec expects a key, not a password. See below
It might be due to a policy restriction that prevents using 32 byte keys. See other answer on that
In your case
The problem is number 1: you are passing the password instead of the key.
AES only supports key sizes of 16, 24 or 32 bytes. You either need to provide exactly that amount or you derive the key from what you type in.
There are different ways to derive the key from a passphrase. Java provides a PBKDF2 implementation for such a purpose.
I used erickson's answer to paint a complete picture (only encryption, since the decryption is similar, but includes splitting the ciphertext):
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec("password".toCharArray(), salt, 65536, 256); // AES-256
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] key = f.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
byte[] ivBytes = new byte[16];
random.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, keySpec, iv);
byte[] encValue = c.doFinal(valueToEnc.getBytes());
byte[] finalCiphertext = new byte[encValue.length+2*16];
System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
System.arraycopy(salt, 0, finalCiphertext, 16, 16);
System.arraycopy(encValue, 0, finalCiphertext, 32, encValue.length);
return finalCiphertext;
Other things to keep in mind:
Always use a fully qualified Cipher name. AES is not appropriate in such a case, because different JVMs/JCE providers may use different defaults for mode of operation and padding. Use AES/CBC/PKCS5Padding. Don't use ECB mode, because it is not semantically secure.
If you don't use ECB mode then you need to send the IV along with the ciphertext. This is usually done by prefixing the IV to the ciphertext byte array. The IV is automatically created for you and you can get it through cipherInstance.getIV().
Whenever you send something, you need to be sure that it wasn't altered along the way. It is hard to implement a encryption with MAC correctly. I recommend you to use an authenticated mode like CCM or GCM.
I was facing the same issue then i made my key 16 byte and it's working properly now. Create your key exactly 16 byte. It will surely work.
You can verify the key length limit:
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
System.out.println("MaxAllowedKeyLength=[" + maxKeyLen + "].");
You can use this code, this code is for AES-256-CBC or you can use it for other AES encryption. Key length error mainly comes in 256-bit encryption.
This error comes due to the encoding or charset name we pass in the SecretKeySpec. Suppose, in my case, I have a key length of 44, but I am not able to encrypt my text using this long key; Java throws me an error of invalid key length. Therefore I pass my key as a BASE64 in the function, and it converts my 44 length key in the 32 bytes, which is must for the 256-bit encryption.
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.Security;
import java.util.Base64;
public class Encrypt {
static byte [] arr = {1,2,3,4,5,6,7,8,9};
// static byte [] arr = new byte[16];
public static void main(String...args) {
try {
// System.out.println(Cipher.getMaxAllowedKeyLength("AES"));
Base64.Decoder decoder = Base64.getDecoder();
// static byte [] arr = new byte[16];
Security.setProperty("crypto.policy", "unlimited");
String key = "Your key";
// System.out.println("-------" + key);
String value = "Hey, i am adnan";
String IV = "0123456789abcdef";
// System.out.println(value);
// log.info(value);
IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
// IvParameterSpec iv = new IvParameterSpec(arr);
// System.out.println(key);
SecretKeySpec skeySpec = new SecretKeySpec(decoder.decode(key), "AES");
// System.out.println(skeySpec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// System.out.println("ddddddddd"+IV);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
// System.out.println(cipher.getIV());
byte[] encrypted = cipher.doFinal(value.getBytes());
String encryptedString = Base64.getEncoder().encodeToString(encrypted);
System.out.println("encrypted string,,,,,,,,,,,,,,,,,,,: " + encryptedString);
// vars.put("input-1",encryptedString);
// log.info("beanshell");
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
Related
I'm making a program which works with messages cryptography by Socket. But, when in my messages has a "o", or "b", or "c" and another letters, i receives that Exception in the decrypto moment.
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
at teste1.Decrypt.decrypt(Decrypt.java:15)
at teste1.Server.main(Server.java:24)
Yep, my message arrives completed with all the characters, so i don't think in some character was lost in the trasmission. So i don't really know what's the problem, because i've tried to changes a lot of things, but i continued recieving this Exception.
Decrypt class:
package teste1;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
public class Decrypt{
String IV = "AAAAAAAAAAAAAAAA";
public String decrypt(String str, String keys) throws Exception{
Cipher decrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(keys.getBytes("UTF-8"), "AES");
decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return new String(decrypt.doFinal(str.getBytes()),"UTF-8");
}
}
If wants the encrypt class too:
package teste1;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encrypt {
String IV = "AAAAAAAAAAAAAAAA";
public byte[] encrypt(String menE, String keys) throws Exception {
Cipher encrypt = Cipher.getInstance("AES/EBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(keys.getBytes("UTF-8"), "AES");
encrypt.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return encrypt.doFinal(menE.getBytes());
}
}
That happens because Strings change your bytes, you should really use Base64
if strings are a must.
If you want to test that run this code:
byte[] aByte = {-45};
System.out.println(Arrays.toString(new String(aByte, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8)));
It will output: [-17, -65, -67] (which is not -45).
Anyways so a few tips for you:
You cannot encrypt with "ECB" and decrypt with "CBC".
An IV should not be a constant. you should generate a new IV for every message and send it along with the message.
Don't specify "UTF-8" use StandardCharsets.UTF_8 (note if using android: StandardCharsets.UTF-8 is API 19+ so you should have a constant for Charset.forName("UTF-8"))
Here is some example code for how to do it with Base64:
public byte[] encrypt(String message, String key, String iv) throws Exception {
Cipher encrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
encrypt.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(Base64.getDecoder().decode(iv)));
return encrypt.doFinal(/*Get bytes from your message*/message.getBytes(StandardCharsets.UTF_8));
}
public String decrypt(String encryptedMessage, String key, String iv) throws Exception{
Cipher decrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
decrypt.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(Base64.getDecoder().decode(iv)));
return new String(decrypt.doFinal(Base64.getDecoder().decode(encryptedMessage)), StandardCharsets.UTF_8);
}
And run it with
//your message
String message = "Hello World!";
//generate a new AES key. (an AES key is just a random sequence 16 bytes)
SecureRandom random = new SecureRandom();
byte[] aesKey = new byte[16];
random.nextBytes(aesKey);
//generate a new initialization vector (iv) which is also a random sequence of 16 bytes.
byte[] iv = new byte[16];
random.nextBytes(iv);
String aesKeyAsString = Base64.getEncoder().encodeToString(aesKey);
String ivAsString = Base64.getEncoder().encodeToString(iv);
//encrypt
byte[] encrypted = encrypt(message, aesKeyAsString, ivAsString);
//enocde your encrypted byte[] to String
String encryptedString = Base64.getEncoder().encodeToString(encrypted);
//decrypt
String decrypted = decrypt(encryptedString, aesKeyAsString, ivAsString);
//print your results
System.out.println("Encrypted: " + encryptedString + " Decrypted: " + decrypted);
Outputs:
Encrypted: |encrypted string depended on the generated key and iv| Decrypted: Hello World!
You can also use the more efficient way and use byte[] instead of Strings but it's your choice.
I am trying to encode in nodejs and decryption for the same in nodejs works well. But when I try to do the decryption in Java using the same IV and secret, it doesn't behave as expected.
Here is the code snippet:
Encryption in nodeJs:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
_ = require('lodash'),
secret = 'd6F3231q7d1942874322a#123nab#392';
function encrypt(text, secret) {
var iv = crypto.randomBytes(16);
console.log(iv);
var cipher = crypto.createCipheriv(algorithm, new Buffer(secret),
iv);
var encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
var encrypted = encrypt("8123497494", secret);
console.log(encrypted);
And the output is:
<Buffer 94 fa a4 f4 a1 3c bf f6 d7 90 18 3f 3b db 3f b9>
94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e
Code Snippet for decryption in JAVA:
public class Test {
public static void main(String[] args) throws Exception {
String s =
"94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e";
String seed = "d6F3231q7d1942874322a#123nab#392";
decrypt(s, seed);
}
private static void decrypt(String s, String seed)
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String parts[] = s.split(":");
String ivString = parts[0];
String encodedString = parts[1];
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] secretBytes = seed.getBytes("UTF-8");
IvParameterSpec ivSpec = new IvParameterSpec(hexStringToByteArray(ivString));
/*Removed after the accepted answer
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);*/
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
cipher.init(Cipher.DECRYPT_MODE, skey, ivSpec);
byte[] output = cipher.doFinal(hexStringToByteArray(encodedString));
System.out.println(new String(output));
}
}
Output: �s˸8ƍ�
I am getting some junk value in the response. Tried a lot of options, but none of them seem to be working. Any lead/help is appreciated.
In your JS code, you're using the 32-character string d6F3231q7d19428743234#123nab#234 directly as the AES key, with each ASCII character directly mapped to a single key byte.
In the Java code, you're instead first hashing the same string with MD5, and then using the MD5 output as the AES key. It's no wonder that they won't match.
What you probably should be doing, in both cases, is either:
randomly generating a string of 32 bytes (most of which won't be printable ASCII characters) and using it as the key; or
using a key derivation function (KDF) to take an arbitrary input string and turn it into a pseudorandom AES key.
In the latter case, if the input string is likely to have less than 256 bits of entropy (e.g. if it's a user-chosen password, most of which only have a few dozen bits of entropy at best), then you should make sure to use a KDF that implements key stretching to slow down brute force guessing attacks.
Ps. To address the comments below, MD5 outputs a 16-byte digest, which will yield an AES-128 key when used as an AES SecretKeySpec. To use AES-256 in Java, you will need to provide a 32-byte key. If trying to use a 32-byte AES key in Java throws an InvalidKeyException, you are probably using an old version of Java with a limited crypto policy that does not allow encryption keys longer than 128 bits. As described this answer to the linked question, you will either need to upgrade to Java 8 update 161 or later, or obtain and install an unlimited crypto policy file for your Java version.
In the Java code you are taking the MD5 hash of secret before using it as a key:
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
Whereas, in your NodeJS code, you don't do this anywhere. So you're using two different keys when encrypting and decrypting.
Don't copy and paste code without understanding it. Especially crypto code.
Faced with the same task (but with 128, it easy to adapt for 256), here is working Java/NodeJs code with comments.
It's additionally wrapped to Base64 to readability, but it's easy to remove if you would like.
Java side (encrypt/decrypt) :
import java.lang.Math; // headers MUST be above the first class
import java.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
// one class needs to have a main() method
public class MyClass
{
private static void log(String s)
{
System.out.print("\r\n"+s);
}
public static SecureRandom IVGenerator() {
return new SecureRandom();
}
// arguments are passed using the text field below this editor
public static void main(String[] args)
{
String valueToEncrypt = "hello, stackoverflow!";
String key = "3e$C!F)H#McQfTjK";
String encrypted = "";
String decrypted = "";
//ENCODE part
SecureRandom IVGenerator = IVGenerator();
byte[] encryptionKeyRaw = key.getBytes();
//aes-128=16bit IV block size
int ivLength=16;
byte[] iv = new byte[ivLength];
//generate random vector
IVGenerator.nextBytes(iv);
try {
Cipher encryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
encryptionCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKeyRaw, "AES"), new IvParameterSpec(iv));
//encrypt
byte[] cipherText = encryptionCipher.doFinal(valueToEncrypt.getBytes());
ByteBuffer byteBuffer = ByteBuffer.allocate(ivLength + cipherText.length);
//storing IV in first part of whole message
byteBuffer.put(iv);
//store encrypted bytes
byteBuffer.put(cipherText);
//concat it to result message
byte[] cipherMessage = byteBuffer.array();
//and encrypt to base64 to get readable value
encrypted = new String(Base64.getEncoder().encode(cipherMessage));
} catch (Exception e) {
throw new IllegalStateException(e);
}
//END OF ENCODE CODE
log("encrypted and saved as Base64 : "+encrypted);
///DECRYPT CODE :
try {
//decoding from base64
byte[] cipherMessageArr = Base64.getDecoder().decode(encrypted);
//retrieving IV from message
iv = Arrays.copyOfRange(cipherMessageArr, 0, ivLength);
//retrieving encrypted value from end of message
byte[] cipherText = Arrays.copyOfRange(cipherMessageArr, ivLength, cipherMessageArr.length);
Cipher decryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKeyRaw, "AES");
decryptionCipher.init(Cipher.DECRYPT_MODE,secretKeySpec , ivSpec);
//decrypt
byte[] finalCipherText = decryptionCipher.doFinal(cipherText);
//converting to string
String finalDecryptedValue = new String(finalCipherText);
decrypted = finalDecryptedValue;
} catch (Exception e) {
throw new IllegalStateException(e);
}
log("decrypted from Base64->aes128 : "+decrypted);
//END OF DECRYPT CODE
}
}
It could be easy be tested by online java compilers (this example prepared on https://www.jdoodle.com/online-java-compiler).
NodeJs decrypt side :
const crypto = require('crypto');
const ivLength = 16;
const algorithm = 'aes-128-ctr';
const encrypt = (value, key) => {
//not implemented, but it could be done easy if you will see to decrypt
return value;
};
function decrypt(value, key) {
//from base64 to byteArray
let decodedAsBase64Value = Buffer.from(value, 'base64');
let decodedAsBase64Key = Buffer.from(key);
//get IV from message
let ivArr = decodedAsBase64Value.slice(0, ivLength);
//get crypted message from second part of message
let cipherTextArr = decodedAsBase64Value.slice(ivLength, decodedAsBase64Value.length);
let cipher = crypto.createDecipheriv(algorithm, decodedAsBase64Key, ivArr);
//decrypted value
let decrypted = cipher.update(cipherTextArr, 'binary', 'utf8');
decrypted += cipher.final('utf8');
return decrypted;
}
PHP code:
<?php
class Crypter {
private $Key;
private $Algo;
private $Mode;
private $Iv;
public function __construct() {
$this->Algo = MCRYPT_BLOWFISH;
$this->Mode = MCRYPT_MODE_CBC;
$this->Key = substr('7890', 0, mcrypt_get_key_size($this->Algo, $this->Mode));
}
public function Encrypt($data) {
$iv_size = mcrypt_get_iv_size($this->Algo, $this->Mode);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$this->Iv = $iv;
$blocksize = mcrypt_get_block_size('blowfish', 'cbc'); // get block size
$pkcs = $blocksize - (strlen($data) % $blocksize); // get pkcs5 pad length
$data.= str_repeat(chr($pkcs), $pkcs); // append pkcs5 padding to the data
$crypt = mcrypt_encrypt($this->Algo, $this->Key, $data, $this->Mode, $this->Iv);
return base64_encode($crypt);
}
public function Decrypt($data) {
$crypt = base64_decode($data);
$decrypt = mcrypt_decrypt($this->Algo, $this->Key, $crypt, $this->Mode, $this->Iv);
$block = mcrypt_get_block_size('blowfish', 'cbc');
$pad = ord($decrypt[($len = strlen($decrypt)) - 1]);
return substr($decrypt, 0, strlen($decrypt) - $pad);
}
}
?>
JAVA code:
package blowfishcbc;
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class BlowfishCBC {
public static void main(String[] args) throws Exception {
String keyString = "7890";
String input = "some data";
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
// for IV
byte[] iv = new byte[cipher.getBlockSize()];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// for key
byte[] keyData = (keyString).getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyData, "Blowfish");
// encrypt
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(input.getBytes("UTF-8"));
String enc = new BASE64Encoder().encode(encrypted);
System.out.println("encrypted: " + new String(enc));
// decrypt
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] dec = new BASE64Decoder().decodeBuffer(enc);
byte[] decrypted = cipher.doFinal(dec);
System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
}
}
I have a scenario where encoding and decoding will be in between two systems like JAVA and PHP. Now the above classes are both examples of PHP and JAVA respectively.
First of all if I use Blowfish algorithm in CBC mode I have to provide a parameter IV (initialization vector). Which is generated randomly and length is equal to the block size. And the block size depends on the encryption mode and algorithm which are being used. Now if I want to decode some data in PHP which was encoded in JAVA then first of all the encryption key must be same as well as the IV (initialization vector).
Now in the java code the IV is generated randomly and if I pass that same IV in the PHP code along with the encoded string (encoded in java) then I get an error in the PHP system that the IV size must be same as the block size.
Now,
for PHP
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
and for JAVA
cipher.getBlockSize();
Both gives the size is 8. But when I pass the IV generated in JAVA to PHP it gives the error of Message: mcrypt_encrypt(): The IV parameter must be as long as the blocksize Now how can I solve this problem.
So far I understand the IV generation process in both system must be same. To be specific the IV length must be same. A little help will be much appreciated. Thanks.
I found a link in stackoverflow here use-3des-encryption-decryption-in-java,but in fact the method uses only two parameter:HG58YZ3CR9" and the "IvParameterSpec iv = new IvParameterSpec(new byte[8]);"
But the most strong option of triple des could use three different key to encrypt the message.So how to do that? I find a mehond in Cipher, which use "SecureRandom" as another parameter.So is this the right way?
The first method code is below:
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class TripleDESTest {
public static void main(String[] args) throws Exception {
String text = "kyle boon";
byte[] codedtext = new TripleDESTest().encrypt(text);
String decodedtext = new TripleDESTest().decrypt(codedtext);
System.out.println(codedtext); // this is a byte array, you'll just see a reference to an array
System.out.println(decodedtext); // This correctly shows "kyle boon"
}
public byte[] encrypt(String message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final byte[] digestOfPassword = md.digest("HG58YZ3CR9"
.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/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
final byte[] plainTextBytes = message.getBytes("utf-8");
final byte[] cipherText = cipher.doFinal(plainTextBytes);
// final String encodedCipherText = new sun.misc.BASE64Encoder()
// .encode(cipherText);
return cipherText;
}
public String decrypt(byte[] message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final byte[] digestOfPassword = md.digest("HG58YZ3CR9"
.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/CBC/PKCS5Padding");
decipher.init(Cipher.DECRYPT_MODE, key, iv);
// final byte[] encData = new
// sun.misc.BASE64Decoder().decodeBuffer(message);
final byte[] plainText = decipher.doFinal(message);
return new String(plainText, "UTF-8");
}
}
As per this document, simply pass the cipher a key that is 168 bits long.
Keysize must be equal to 112 or 168.
A keysize of 112 will generate a Triple DES key with 2 intermediate keys, and a keysize of 168 will generate a Triple DES key with 3 intermediate keys.
Your code seems to do something questionable to make up for the fact that the output of MD5 is only 128 bits long.
Copy-pasting cryptographic code off the internet will not produce secure applications. Using a static IV compromises several reasons why CBC mode is better than ECB. If you are using a static key, you should probably consider generating random bytes using a secure random number generator instead of deriving the key from a short ASCII string. Also, there is absolutely no reason to use Triple DES instead of AES in new applications.
In principle, the for-next loop to generate the DES ABA key does seem correct. Note that you can provide DESede with a 16 byte key from Java 7 onwards, which amounts to the same thing.
That said, the code you've shown leaves a lot to be desired:
I is not secure:
the key is not generated by a Password Based Key Derivation Function (PBKDF) using the (password?) string
the key is composed of two keys instead of three (using a triple DES or TDEA with an ABA key)
the IV is set to all zero's instead of being randomized
the "password" string is too short
Furthermore the following code mistakes can be seen:
using new sun.misc.BASE64Encoder() which is in the Sun proprietary packages (which can be removed or changed during any upgrade of the runtime)
throwing Exception for platform exceptions and runtime exceptions (not being able to decrypt is handled the same way as not being able to instantiate the Cipher)
requesting 24 bytes instead of 16 within the Arrays.copyOf() call (which seems to return 24 SHA-1 output while there are only 20 bytes)
To generate a 3DES 24 byte (168 bits used) DES ABC key from a password (like) String you should use PBKDF-2. Adding an authentication tag is also very important if man-in-the-middle attacks or padding oracle apply. It would be much secure and much more practical to upgrade to AES if you can control the algorithms being used as well.
I am trying to learn and test the java 1.6 encryption/decryption API. I want to know what I am doing wrong and what I am missing in terms of knowledge.
In the code that follows below, I create two ciphers: one to encrypt and another to decrypt. When I use these ciphers, I initialize them with different SecretKey's, but I am still able to get the same value back out. Why is this?
String algorithm = "DES";
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
byte[] encBytes = "12345678".getBytes("UTF8");
byte[] decBytes = "56781234".getBytes("UTF8");
DESKeySpec keySpecEncrypt = new DESKeySpec(encBytes);
DESKeySpec keySpecDecrypt = new DESKeySpec(decBytes);
SecretKey keyEncrypt = keyFactory.generateSecret(keySpecEncrypt);
SecretKey keyDecrypt = keyFactory.generateSecret(keySpecDecrypt);
Cipher cipherEncrypt = Cipher.getInstance(algorithm);
Cipher cipherDecrypt = Cipher.getInstance(algorithm);
String input = "john doe";
cipherEncrypt.init(Cipher.ENCRYPT_MODE, keyEncrypt);
byte[] inputBytes = cipherEncrypt.doFinal(input.getBytes());
System.out.println("inputBytes: " + new String(inputBytes));
cipherDecrypt.init(Cipher.DECRYPT_MODE, keyDecrypt);
byte[] outputBytes = cipherDecrypt.doFinal(inputBytes);
System.out.println("outputBytes: " + new String(outputBytes));
Welcome to encryption! As mentioned DES is symmetric and requires the same key for encryption as decryption. That key needs to be the right number of bits for the cipher that you're using. For DES that's 56-bit. Before you go too far with that though, here are a few things you might want to consider:
You should use a stronger encryption standard like AES. It's possible to break DES encryption now.
If you want to use a string as the key, then you should use a strong hash function like SHA-256 against that key string. Then take as many bits from that hash output as you need for the encryption key, 128-bit is plenty sufficient for AES. Your key string should be long like you have.
It'll be best to use a block cipher mode that doesn't generate the same output for the same input each time. See block cipher modes of operation for info and a visualization of why ECB mode is bad.
Here's a working example of using 128-bit AES encryption in CBC mode with PKCS #5 padding:
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class EncryptDecrypt {
public static void main(String[] args) throws Exception {
// here are your inputs
String keyString = "averylongtext!#$##$##$#*&(*&}{23432432432dsfsdf";
String input = "john doe";
// setup AES cipher in CBC mode with PKCS #5 padding
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// setup an IV (initialization vector) that should be
// randomly generated for each input that's encrypted
byte[] iv = new byte[cipher.getBlockSize()];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// hash keyString with SHA-256 and crop the output to 128-bit for key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(keyString.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// encrypt
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(input.getBytes("UTF-8"));
System.out.println("encrypted: " + new String(encrypted));
// include the IV with the encrypted bytes for transport, you'll
// need the same IV when decrypting (it's safe to send unencrypted)
// decrypt
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
}
}
Here's the description from JDK doc:
DESKeySpec
public DESKeySpec(byte[] key)
throws InvalidKeyException
Creates a DESKeySpec object using the first 8 bytes in key as the key material for the DES key.
The bytes that constitute the DES key are those between key[0] and key[7] inclusive.
DESKeySpec uses only the first 8 bytes of byte[] as key. Thus the actual keys in used are identical in your example.
Here's a working example of using 56-bit DES encryption.
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class CipherHelper {
// Algorithm used
private final static String ALGORITHM = "DES";
/**
* Encrypt data
* #param secretKey - a secret key used for encryption
* #param data - data to encrypt
* #return Encrypted data
* #throws Exception
*/
public static String cipher(String secretKey, String data) throws Exception {
// Key has to be of length 8
if (secretKey == null || secretKey.length() != 8)
throw new Exception("Invalid key length - 8 bytes key needed!");
SecretKey key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
return toHex(cipher.doFinal(data.getBytes()));
}
/**
* Decrypt data
* #param secretKey - a secret key used for decryption
* #param data - data to decrypt
* #return Decrypted data
* #throws Exception
*/
public static String decipher(String secretKey, String data) throws Exception {
// Key has to be of length 8
if (secretKey == null || secretKey.length() != 8)
throw new Exception("Invalid key length - 8 bytes key needed!");
SecretKey key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(toByte(data)));
}
// Helper methods
private static byte[] toByte(String hexString) {
int len = hexString.length()/2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
return result;
}
public static String toHex(byte[] stringBytes) {
StringBuffer result = new StringBuffer(2*stringBytes.length);
for (int i = 0; i < stringBytes.length; i++) {
result.append(HEX.charAt((stringBytes[i]>>4)&0x0f)).append(HEX.charAt(stringBytes[i]&0x0f));
}
return result.toString();
}
private final static String HEX = "0123456789ABCDEF";
// Helper methods - end
/**
* Quick test
* #param args
*/
public static void main(String[] args) {
try {
String secretKey = "01234567";
String data="test";
String encryptedData = cipher(secretKey, data);
System.out.println("encryptedData: " + encryptedData);
String decryptedData = decipher(secretKey, encryptedData);
System.out.println("decryptedData: " + decryptedData);
} catch (Exception e) {
e.printStackTrace();
}
}
}