Encrypting and Decrypting Using Java: Unable to get same output - java

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();
}
}
}

Related

Decryption not returning correct plaintext [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
I am trying to create a simple AES encryption/decryption module for use in a larger project, but I'm having trouble getting the AES methods to work. I've done a good amount of research, but I can't figure out what is going wrong within my code (I'm suspecting something simple that I'm missing).
Main:
public static byte[] genKey() {
// Create key generator
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance("AES");
}
catch(GeneralSecurityException e) {
e.printStackTrace();
return null;
}
// Create random byte generator
SecureRandom r = new SecureRandom();
// Initialize key generator
keyGen.init(256, r);
SecretKey key = keyGen.generateKey();
return key.getEncoded();
}
public static void main(String[] args) throws GeneralSecurityException {
// write your code here
// Create AES handler
AES aes = new AES();
// Generate key
byte[] key = genKey();
// Set key for AES
aes.setKey(key);
Scanner in = new Scanner(System.in);
System.out.print("Please enter a phrase to encrypt: ");
String input = in.nextLine();
// Encrypt phrase
byte[][] encrypted = aes.encrypt(input);
// Decrypt phrase
String plaintext = aes.decrypt(encrypted[0], encrypted[1]);
// Print results
System.out.println("Ciphertext: " + encrypted[1]);
System.out.println("Plaintext: " + plaintext);
}
AES:
private Cipher cipher;
private SecretKey key;
public AES() {
// Create Cipher
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
public void setKey(byte[] key) {
this.key = new SecretKeySpec(key, "AES");
}
public byte[][] encrypt(String plaintext) throws GeneralSecurityException {
System.out.println("Using key : " + key.getEncoded() + " to encrypt");
byte[][] values = new byte[2][];
// Decode plaintext into bytes
byte[] decodedPlaintext = new byte[0];
try {
decodedPlaintext = plaintext.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// Generate an IV and set up the Cipher to encrypt
byte[] ivBytes = new byte[16];
SecureRandom rand = new SecureRandom();
rand.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
// Encrypt decoded plaintext
byte[] ciphertext = cipher.doFinal(decodedPlaintext);
values[0] = ivBytes;
values[1] = ciphertext;
return values;
}
public String decrypt(byte[] ivBytes, byte[] ciphertext) throws GeneralSecurityException {
System.out.println("Using key " + key.getEncoded() + " to decrypt");
// Set up cipher to decrypt
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decodedPlaintext = cipher.doFinal(ciphertext);
// Encode plaintext
String plaintext = Base64.getEncoder().encodeToString(decodedPlaintext);
return plaintext;
}
Results:
Please enter a phrase to encrypt: test
Using key : [B#442d9b6e to encrypt
Using key [B#3d012ddd to decrypt
Ciphertext: [B#515f550a
Plaintext: dGVzdA==
I don't understand why my encryption/decryption seem to be using different keys when I only set the key once in the beginning. Am I creating a key wrong?
I've also tried:
byte[] key = new byte[32];
SecureRandom r = new SecureRandom();
r.nextBytes(key);
// Set key for AES
aes.setKey(key);
And run into the same issue.
If you trace this.key in AES class all the way through encode and decode using Arrays.toString(this.key.getEncoded()) it does look like it's using a persistent key.
this.key.getEncoded().toString()) returns a different representation each time but it seems like the actual byte array is the same.
I replaced
String plaintext = Base64.getEncoder().encodeToString(decodedPlaintext);
with
String plaintext = new String(decodedPlaintext, StandardCharsets.UTF_8);
and it seemed to work.

Encryption/decryption: HMAC tags don't match in decryption method

I'm trying to code encryption and decryption methods that will encrypt/decrypt a message.
In the encrypt method, it will take in a string. It will read in a public key and be used in a RSA cipher. Then, the message will be encrypted with an AES Cipher that has a AES Key and IV. Then, a HMAC tag will be generated with the ciphertext encrypted AES by using a HMAC key. The AES Key and HMAC key are concatenated together and encrypted by RSA Cipher. The method will return a JSONObject that contains: RSA ciphertext, AES ciphertext, the AES IV, and HMAC tag. They are byte arrays that are converted into hex strings.
In the decrypt method, it will take in the JSON Object, which will be parsed. It will read in a private key that will be used in a RSA cipher. The RSA cipher will be used to decrypt the concatenated keys. Once decrypted the keys will be separated to AES Key and HMAC key. Then, new HMAC tag will be generated on the AES Ciphertext. Compare the tag from the encryption and the new tag. If they are equal, then decrypt the AES ciphertext and get the message.
When I run my code, there are no errors, but it does not decrypt because the 2 tags don't match. I don't know why.
The public and private keys are .der files that were converted from .pem files.
Please help me. Thanks!
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import org.json.*;
import javax.xml.bind.DatatypeConverter;
public class CryptographicTools
{
/**
* This method encrypts a message
* #param message String message to be encrypted
* #return a JSONObject
*/
public JSONObject encryptMessage(String message)
{
JSONObject output = new JSONObject(); // instantiate JSONObject
try
{
//read in public key
byte[] publicKeyBytes = readKeyFromFile("public.der");//pem convert to der
//turn bytes into public key
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes); //encodes the bytes
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); //make the key a RSA instance
//initialize RSA object and public key
Cipher RSAObject = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); //with OAEP
RSAObject.init(Cipher.ENCRYPT_MODE, keyFactory.generatePublic(publicSpec)); //create RSA encryption cipher with a generated public key
//generate 256-bit AES key
KeyGenerator keyGen = KeyGenerator.getInstance("AES");//generate AES Key
keyGen.init(256); //generate a key with 256 bits
SecretKey AESKey = keyGen.generateKey(); //generate AES key with 256 bits
//Create AES IV
SecureRandom randomByteGenerator = new SecureRandom();//secure generator to generate random byes for IV
byte[] AESKeyIVArray = new byte[16];
randomByteGenerator.nextBytes(AESKeyIVArray);//get random bytes for iv
IvParameterSpec AES_IV = new IvParameterSpec(AESKeyIVArray); //iv object for AES object
//initialize AES object
Cipher AESObject = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESObject.init(Cipher.ENCRYPT_MODE, AESKey, AES_IV); //tell the AES object to encrypt
//encrypt message with AES
byte[] AESciphertext = AESObject.doFinal(message.getBytes());
//generate 256-bit HMAC key
byte[] SHA256KeyArray = new byte[32];//256 bits
randomByteGenerator.nextBytes(SHA256KeyArray);//generate random bits for key
SecretKeySpec HMACKeySpec = new SecretKeySpec (SHA256KeyArray,"HmacSHA256"); //make the key
Mac HMAC = Mac.getInstance("HmacSHA256"); //initialize HMAC
HMAC.init(HMACKeySpec);//put key in cipher
byte [] HMACTag = HMAC.doFinal(AESciphertext);//generate HMAC tag
//concatenate AES and HMAC keys
byte[] AESKeyByte = AESKey.getEncoded();///turn AESKey to byte array
byte[] HMACKeySpecByte = HMACKeySpec.getEncoded();///turn HMAXKey to byte array
byte[] concatenatedKeys = new byte[AESKeyByte.length + HMACKeySpecByte.length];//new array for concatenated keys
//combine keys in new array
System.arraycopy(AESKeyByte, 0, concatenatedKeys, 0, AESKeyByte.length);
System.arraycopy(HMACKeySpecByte, 0, concatenatedKeys, AESKeyByte.length, HMACKeySpecByte.length);
//encrypt keys with RSA object
byte[] RSAciphertext = RSAObject.doFinal(concatenatedKeys);
//put RSA ciphertext, AES ciphertext, AES_IV and HMAC tag in JSon
//save byte[] as Strings in hex
output.put("RSAciphertext", DatatypeConverter.printHexBinary(RSAciphertext));
output.put("AESciphertext", DatatypeConverter.printHexBinary(AESciphertext));
output.put("AES_IV", DatatypeConverter.printHexBinary(AES_IV.getIV()));
output.put("HMACTag", DatatypeConverter.printHexBinary(HMACTag));
}
catch (Exception e)
{
System.out.println("Error: " + e.toString() +e.getMessage()); //error message
}
return output; //return as JSON Object
}
/**
* This method decrypts a message
* #param jsonObjectEncrypted
* #return message as string
*/
public String decrypt (JSONObject jsonObjectEncrypted)
{
String message="";
try
{
//recover RSA ciphertext from JSON
String RSACiphertextString=jsonObjectEncrypted.getString("RSAciphertext");
byte[] recoveredRSAciphertext = DatatypeConverter.parseHexBinary(RSACiphertextString); //convert hex string to byte array
//recover AES ciphertext from JSON
String AESCiphertextString=jsonObjectEncrypted.getString("AESciphertext");
byte[] recoveredAESciphertext = DatatypeConverter.parseHexBinary(AESCiphertextString); //convert hex string to byte array
//recover AES IV from JSON
String AES_IVString=jsonObjectEncrypted.get("AES_IV").toString();
byte[] recoveredAES_IV = DatatypeConverter.parseHexBinary(AES_IVString); //convert hex string to byte array
//recover HMACTag from JSON
String HMACTagString=jsonObjectEncrypted.getString("HMACTag");
byte[] recoveredHMACTag = DatatypeConverter.parseHexBinary(HMACTagString); //convert hex string to byte array
//read in private key
byte[] privateKeyBytes = readKeyFromFile("private.der");//pem convert to der
//turn bytes into private key
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//initialize RSA object and private key
Cipher RSAObject = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); //with OAEP
RSAObject.init(Cipher.DECRYPT_MODE, keyFactory.generatePrivate(privateSpec)); //create RSA encryption cipher with a generated private key
//Decrypt concatenated keys with RSA object
byte[] concatenatedKeys = RSAObject.doFinal(recoveredRSAciphertext);
//split the concatenated keys
byte[] AESKey = new byte[concatenatedKeys.length/2];
byte[] HMACKey = new byte[concatenatedKeys.length/2];
System.arraycopy(concatenatedKeys, 0,AESKey,0,AESKey.length); //Copy half into AESKey
System.arraycopy(concatenatedKeys, AESKey.length,HMACKey,0,HMACKey.length); //Copy Other half into HMACKey
//generate HMACTag
SecretKeySpec HMACKeySpec = new SecretKeySpec (HMACKey,"HmacSHA256"); //make the key
Mac HMAC = Mac.getInstance("HmacSHA256");
HMAC.init(HMACKeySpec);//initialize with HMAC Key
byte [] newHMACTag = HMAC.doFinal(recoveredAESciphertext); //generate HMACTag with AES Ciphertext
if(recoveredHMACTag.equals(newHMACTag)) //encrypt message if tags are equal
{
//initialize AES object
Cipher AESObject = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESObject.init(Cipher.DECRYPT_MODE, new SecretKeySpec (AESKey,"AES"), new IvParameterSpec(recoveredAES_IV)); //tell the AES object to encrypt
message = new String (AESObject.doFinal(recoveredAESciphertext), "US-ASCII");//encrypt AES ciphertext and save as string
}
else
{
System.out.println("Message cannot be decrypted.");
}
}
catch (Exception e)
{
System.out.println("Error: "+e.toString()+": "+e.getMessage()); //error message
}
return message; //return plaintext
}
/**
* This method reads bytes of a key from a file into a byte array
* #param fileName type of key
* #return byte array
* #throws IOException
*/
public byte[] readKeyFromFile(String fileName) throws IOException
{
return Files.readAllBytes(Paths.get(fileName));
}
}
The Java array doesn't implement .equals() the way you'd want it to (info). Try replacing this check:
recoveredHMACTag.equals(newHMACTag)
by this one:
java.util.Arrays.equals(recoveredHMACTag, newHMACTag)
I can't say that's all that could be causing it to go wrong, but it's the first thing I would check.

Java AES encryption and decryption with static secret

I have an application that needs to store some secret passwords in a configuration file such as database and ftp passwords/detail. I've looked around and found a lot of encryption/decryption solutions using AES, but I can't seem to figure out how to make it work without changing the key. That means I can encrypt and decrypt (using the same SecretKey), but to maintain persistence across restarts etc. I can't seem to make the SecretKey stay the same. The example below shows my methods working:
String secret = Encryptor.encrpytString("This is secret");
String test = Encryptor.decrpytString(secret);
System.out.println(test); //This is secret is printed
So far so good. However if I run it once I might get the value of '2Vhht/L80UlQ184S3rlAWw==' as my secret, the next time it is 'MeC4zCf9S5wUUKAu8rvpCQ==', so presumably the key is changing. I'm assuming I am applying some counter-intuative logic to the problem and would appreciate if someone can shed some light on either a) what I'm doing wrong, or b) a solution that would allow me to store the password information encrypted and retrievable with the information provided.
My methods are as follows:
private static final String salt = "SaltySalt";
private static byte [] ivBytes = null;
private static byte[] getSaltBytes() throws Exception {
return salt.getBytes("UTF-8");
}
private static char[] getMasterPassword() {
return "SuperSecretPassword".toCharArray();
}
private static byte[] getIvBytes() throws Exception {
if (ivBytes == null) {
//I don't have the parameters, so I'll generate a dummy encryption to create them
encrpytString("test");
}
return ivBytes;
}
public static String encrpytString (String input) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536,256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8"));
return DatatypeConverter.printBase64Binary(encryptedTextBytes);
}
public static String decrpytString (String input) throws Exception {
byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(input);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536, 256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(getIvBytes()));
byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
return new String(decryptedTextBytes);
}
Thanks for the help!
OK, looks like I've found the answer to my question. I sourced my information from this Stackoverflow post.
From what I understand, the IV (initialisation vector) is used to add entropy into the encryption process. Each time you create a new cipher, Java creates a slightly different IV. There are therefore two solutions:
User a fixed IV, or
Store the IV along with the encrypted data.
From what I've read, option 1 is not very good practice; so option 2 it is. I understand that it should be possible to simply append the IV to the encrypted string (as the secret is still required) and therefore the IV can be reconstructed when it comes time to decrypt.
Here is the almost complete solution. I'm still getting some padding errors on decryption (see my comment). I don't have time to spend on it now, so as a temporary measure I immediately try decrypting an encrypted string and keep on trying (iterating) until it works. It seems to have about a 50% hit rate + I'm not encrypting often enough for it to be a performance concern. Would be nice if someone could suggest a fix though (just for completeness sake).
private static final String salt = "SaltySalt";
private static final int IV_LENGTH = 16;
private static byte[] getSaltBytes() throws Exception {
return salt.getBytes("UTF-8");
}
private static char[] getMasterPassword() {
return "SuperSecretPassword".toCharArray();
}
public static String encrpytString (String input) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536,256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8"));
byte[] finalByteArray = new byte[ivBytes.length + encryptedTextBytes.length];
System.arraycopy(ivBytes, 0, finalByteArray, 0, ivBytes.length);
System.arraycopy(encryptedTextBytes, 0, finalByteArray, ivBytes.length, encryptedTextBytes.length);
return DatatypeConverter.printBase64Binary(finalByteArray);
}
public static String decrpytString (String input) throws Exception {
if (input.length() <= IV_LENGTH) {
throw new Exception("The input string is not long enough to contain the initialisation bytes and data.");
}
byte[] byteArray = DatatypeConverter.parseBase64Binary(input);
byte[] ivBytes = new byte[IV_LENGTH];
System.arraycopy(byteArray, 0, ivBytes, 0, 16);
byte[] encryptedTextBytes = new byte[byteArray.length - ivBytes.length];
System.arraycopy(byteArray, IV_LENGTH, encryptedTextBytes, 0, encryptedTextBytes.length);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536, 256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
return new String(decryptedTextBytes);
}
Use a static Initialization Vector, e.g. a zero IV:
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
Since you're storing passwords you probably want to use a random IV and/or random salt and store them with the cipher text so the same passwords don't encrypt to the same ciphertext.
You need to setSeed() before
class Encryptor {
static final String salt = "SaltSalt";
public static byte[] encryptString(String input) throws Exception {
byte[] bytes = input.getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("AES");
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed(salt.getBytes("UTF-8"));
keyGenerator.init(256, secureRandom);
Key key = keyGenerator.generateKey();
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] a = cipher.doFinal(bytes);
return a;
}
public static String decryptString(byte[] input) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed(salt.getBytes("UTF-8"));
keyGenerator.init(256, secureRandom);
Key key = keyGenerator.generateKey();
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(input);
String result = new String(decrypted, "UTF-8");
return result;
}
}

Android: Having issues saving encrypted username/password

I have the following class I use to store encrypted preferences to use with my application (using interface with 3rd part site which does not support OAuth)...
public class CryptoTranslator {
private static SecretKey SEC_KEY;
/**
* #return the sEC_KEY
*/
public static SecretKey getSEC_KEY() {
return SEC_KEY;
}
public static String getSEC_KEY_String(){
return Base64.encodeToString(SEC_KEY.getEncoded(), Base64.DEFAULT);
}
/**
* #param sEC_KEY the sEC_KEY to set
*/
public static void setSEC_KEY(SecretKey sEC_KEY) {
SEC_KEY = sEC_KEY;
}
public static void setSEC_KEY_STRING(String sEC_KEY){
byte[] key = Base64.decode(sEC_KEY, Base64.DEFAULT);
SEC_KEY = new SecretKeySpec(key, 0, key.length, "AES");
}
public static void generateKey() throws NoSuchAlgorithmException {
// Generate a 256-bit key
final int outputKeyLength = 256;
SecureRandom secureRandom = new SecureRandom();
// Do *not* seed secureRandom! Automatically seeded from system entropy.
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(outputKeyLength, secureRandom);
SecretKey key = keyGenerator.generateKey();
SEC_KEY = key;
}
private static byte[] getRawKey() throws Exception {
if (SEC_KEY == null){
generateKey();
}
byte[] raw = SEC_KEY.getEncoded();
return raw;
}
/**
*
*
* #param clear clear text string
* #param mode this should either be Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE
* #return
* #throws Exception
*/
private static String translate(String clear, int mode) throws Exception {
if(mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException("Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(mode, skeySpec);
byte[] encrypted = cipher.doFinal(clear.getBytes());
return new String(encrypted);
}
public static String encrypt(String clear) throws Exception {
return translate(clear,Cipher.ENCRYPT_MODE);
}
public static String decrypt(String encrypted) throws Exception {
return translate(encrypted,Cipher.DECRYPT_MODE);
}
}
So now I have encrypted and stored the data. Now I want to pull it out...
String secString = settings.getString(SEC_KEY, null);
if (secString == null) {
try {
CryptoTranslator.generateKey();
settings.edit()
.putString(SEC_KEY,
CryptoTranslator.getSEC_KEY_String()).commit();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
CryptoTranslator.setSEC_KEY_STRING(secString);
}
try {
getUserNamePassword();
} catch (Exception ex) {
Log.i("Preferences",
"There was an issue getting username and password");
isStored = CRED_STATUS_DEF;
}
...
private static void getUserNamePassword() throws Exception {
isStored = settings.getBoolean(CRED_STATUS, CRED_STATUS_DEF);
if (isStored) {
if (settings.contains(USERNAME_KEY))
username = settings.getString(USERNAME_KEY, "");
if (settings.contains(PASSWORD_KEY))
password = settings.getString(PASSWORD_KEY, "");
}
isUsernamePasswordValid();
if (isStored) {
String username2 = CryptoTranslator.decrypt(username);
Log.d("Security", "Username encrypted");
String password2 = CryptoTranslator.decrypt(password);
username = username2;
password = password2;
Log.d("Security", "Password encrypted");
}
}
But this gives me the following error....
javax.crypto.IllegalBlockSizeException: last block incomplete in decryption
Can someone see what I am doing wrong?
Update
Ok per the response I went ahead and changed my code to the following...
public static final int IV_LENGTH = 16;
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
...
private static String translate(String clear, int mode) throws Exception {
if (mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException(
"Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(generateIv());
cipher.init(mode, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(clear.getBytes());
return new String(encrypted);
}
...
private static byte[] generateIv() throws NoSuchAlgorithmException,
NoSuchProviderException {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
Now I get...
javax.crypto.BadPaddingException: pad block corrupted
To try and use hex changed to...
private static byte[] translate(byte[] val, int mode) throws Exception {
if (mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException(
"Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(generateIv());
cipher.init(mode, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(val);
return encrypted;
}
This seems to almost work (I am getting the .com back) but the chars are still pretty jumbled.
public static String encrypt(String clear) throws Exception {
byte[] test = translate(clear.getBytes(), Cipher.ENCRYPT_MODE);
return new String(Hex.encodeHex(test));
}
public static String decrypt(String encrypted) throws Exception {
return new String(translate(Hex.decodeHex(encrypted.toCharArray()), Cipher.DECRYPT_MODE));
}
*The converting to Hex and back is screwed up here.
So there are a couple of issues with your code.
First is the output of an AES cipher is not character data, you are mangling your ciphertext by trying to put it in a String. When you try to decrypt your mangled ciphertext it is now the wrong length. You need to Base64 or Hex encode the ciphertext if you want to store it in a String and then decode it back in to a byte[] before decrypting it.
Second, when you specify just AES for your cipher spec Java expands that to AES/ECB/PKCS5Padding. ECB is an insecure cipher mode if you intend to encrypt more than 1 block of data (16 bytes for AES). I recommend you switch to a different spec AES/CBC/PKCS5Padding should be acceptable. Using a mode other than ECB will require an Initialization Vector (IV). The IV should be randomly generated but does not need to be secret, so you can store as plaintext with your ciphertext as you'll need it to decrypt as well. The initialization vector needs to be one block in length (16 bytes for AES). Do not reuse the same IV with the same AES key ever, generate a new IV for each encryption being done.
Finally, if your going to store IV + ciphertext in a third party service I recommend you add a MAC (such as HMACSHA1). A MAC will ensure the integrity of your IV + ciphertext before you attempt to decrypt it. A MAC will require a secret key as well, and you should not use the same key you generated for the cipher itself. You can prepend the generated MAC to your IV + ciphertext, so now you are storing MAC + IV + ciphertext.
Android AES client side + PHP AES server side it will throw this error :)
The solution is:
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Please search over the internet for the full source code. I am under NDA and to lazzy to make anonymous my whole code regarding this part, but I am sure you will find it.

How to fix the NoSuchAlgorithmException in Java when using Blowfish?

So I'm writing a program to encrypt and decrypt text files but I seem to be always getting this error when I use an encrypthion other than "Blowfish" (e.g. "Blowfish/CBC/PKCS5Padding"). The excepthiong I get is:
Exception in thread "main" java.security.NoSuchAlgorithmException: Blowfish/CBC/PKCS5Padding KeyGenerator not available
at javax.crypto.KeyGenerator.<init>(DashoA13*..)
at javax.crypto.KeyGenerator.getInstance(DashoA13*..)
at Encryptor.<init>(Encryptor.java:87)
at Encryptor.main(Encryptor.java:30)
A portion of my code:
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encryptor2 {
private IvParameterSpec ivSpec;
private SecretKeySpec keySpec;
private Cipher cipher;
public static void main(String[] args) throws Exception {
Encryptor2 e = new Encryptor2(
"averylongtext!#$##$##$#*&(*&}{23432432432dsfsdf");
String enc = e.encrypt("john doe");
String dec = e.decrypt(enc);
}
public Encryptor2(String pass) throws Exception {
// setup AES cipher in CBC mode with PKCS #5 padding
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);
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(pass.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
keySpec = new SecretKeySpec(key, "AES");
}
public String encrypt(String original) throws Exception {
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(original.getBytes("UTF-8"));
System.out.println("encrypted: `" + new String(encrypted) + "`");
return new String(encrypted);
}
public String decrypt(String encrypted) throws Exception {
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decrypted = cipher.doFinal(encrypted.getBytes("UTF-8"));
System.out.println("decrypted: `" + new String(decrypted, "UTF-8")
+ "`");
return new String(decrypted, "UTF-8");
}
}
But now it fails with Input length must be multiple of 16 when decrypting with padded cipher
The additional parameters that you're specifying with the algorithm are meant for Cipher. For KeyGenerator and SecretKeySpec you only specify the algorithm. The other parameters are for the cipher mode of operation and padding to be used. For example, if you're using Blowfish in CBC mode with PKCS #5 padding you want:
KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
See Encrypting and Decrypting Using Java: Unable to get same output for an example. It uses the same mode and padding as you have. The only difference is that uses AES instead of Blowfish, however it works exactly the same.

Categories