I am still searching for a good solution of my encryption needs. I found a solution on the internet that I reworked to fit the program needs but the encryption fails. I don't know why. Any suggestions?
Dear Maarten Bodeswes I tried your solution, but could not get it to work stable. I send the encrypted data via php to a database and get it back via php. I figured I have to exchange the plus-signs with something else, but it still would not work stable.
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
public class AESplus
{
// AES specification
private static final String CIPHER_SPEC = "AES/CBC/PKCS5Padding";
// Key derivation specification
private static final String KEYGEN_SPEC = "PBKDF2WithHmacSHA1";
private static final int SALT_LENGTH = 16; // in bytes
private static final int ITERATIONS = 32768;
private static final int KEY_LENGTH = 128;
private static final String SPLITCHAR = "###";
public static SecretKey makeKey(String kennwort) throws NoSuchAlgorithmException, InvalidKeySpecException
{
char[] password = kennwort.toCharArray();
// generate salt
byte[] salt = generateSalt(SALT_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_SPEC);
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
return tmp;
}
private static byte[] generateSalt(int length)
{
Random r = new SecureRandom();
byte[] salt = new byte[length];
r.nextBytes(salt);
return salt;
}
public static String encrypt(String input, SecretKey key) throws InvalidKeyLengthException, StrongEncryptionNotAvailableException,
IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException,
IllegalBlockSizeException, BadPaddingException
{
StringBuilder output = new StringBuilder();
// initialize AES encryption
Cipher encrypt = null;
encrypt = Cipher.getInstance(CIPHER_SPEC);
encrypt.init(Cipher.ENCRYPT_MODE, key);
// get initialization vector
byte[] iv = encrypt.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] encrypted = encrypt.update(input.getBytes());
output.append(HexUtils.toHex(encrypted));
encrypted = encrypt.doFinal();
if (encrypted != null)
{
// write authentication and AES initialization data
output.append(HexUtils.toHex(iv) + SPLITCHAR);
// data
output.append(HexUtils.toHex(encrypted));
}
return output.toString();
}
public static String decrypt(String input, SecretKey schlüssel) throws InvalidPasswordException, InvalidAESStreamException,
IOException, StrongEncryptionNotAvailableException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
String[] inputArray = input.split(SPLITCHAR);
// initialize AES decryption
byte[] iv = new byte[16]; // 16-byte I.V. regardless of key size
iv = HexUtils.toBytes(inputArray[0]);
Cipher decrypt = Cipher.getInstance(CIPHER_SPEC);
decrypt.init(Cipher.DECRYPT_MODE, schlüssel, new IvParameterSpec(iv));
// read data from input into buffer, decrypt and write to output
byte[] hexInput = HexUtils.toBytes(inputArray[1]);
byte[] decrypted = decrypt.update(hexInput);
StringBuilder output = new StringBuilder();
output.append(new String(decrypted));
// finish decryption - do final block
decrypted = decrypt.doFinal();
if (decrypted != null)
{
output.append(new String(decrypted));
}
return output.toString();
}
// ******** EXCEPTIONS thrown by encrypt and decrypt ********
/**
* Thrown if an attempt is made to decrypt a stream with an incorrect
* password.
*/
public static class InvalidPasswordException extends Exception
{
private static final long serialVersionUID = 1L;
}
/**
* Thrown if an attempt is made to encrypt a stream with an invalid AES key
* length.
*/
public static class InvalidKeyLengthException extends Exception
{
private static final long serialVersionUID = 1L;
InvalidKeyLengthException(int length)
{
super("Invalid AES key length: " + length);
}
}
/**
* Thrown if 192- or 256-bit AES encryption or decryption is attempted,
* but not available on the particular Java platform.
*/
public static class StrongEncryptionNotAvailableException extends Exception
{
private static final long serialVersionUID = 1L;
public StrongEncryptionNotAvailableException(int keySize)
{
super(keySize + "-bit AES encryption is not available on this Java platform.");
}
}
/**
* Thrown if an attempt is made to decrypt an invalid AES stream.
*/
public static class InvalidAESStreamException extends Exception
{
private static final long serialVersionUID = 1L;
public InvalidAESStreamException()
{
super();
};
public InvalidAESStreamException(Exception e)
{
super(e);
}
}
}
This piece of your code puts the IV into the middle of the output:
byte[] encrypted = encrypt.update(input.getBytes());
output.append(HexUtils.toHex(encrypted));
encrypted = encrypt.doFinal();
if (encrypted != null)
{
// write authentication and AES initialization data
output.append(HexUtils.toHex(iv) + SPLITCHAR);
// data
output.append(HexUtils.toHex(encrypted));
}
To work with your decryption method it needs to be at the beginning.
Related
This question already has answers here:
What's the simplest way to print a Java array?
(37 answers)
Closed 1 year ago.
The way i understand it, a key encoding should return a sequence of bytes in some specific encoding such as UTF-8 for example.
However, logging the following:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
Log("TEST : ${kp.public.encoded}")
Log("Test : ${kp.public.encoded}")
Equivalently, in Java:
kp.getPublic().getEncoded();
Is giving me 2 different byte arrays! What am i missing here?
It seems that every time i call the encoded method, different pairs are generated.
Try in this way
import android.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AESEncyption {
private static final int pswdIterations = 10;
private static final int keySize = 128;
private static final String cypherInstance = "AES/CBC/PKCS5Padding";
private static final String secretKeyInstance = "PBKDF2WithHmacSHA1";
private static final String plainText = "sampleText";
private static final String AESSalt = "exampleSalt";
private static final String initializationVector = "8119745113154120";
public static String encrypt(String textToEncrypt) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(getRaw(plainText, AESSalt), "AES");
Cipher cipher = Cipher.getInstance(cypherInstance);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(initializationVector.getBytes()));
byte[] encrypted = cipher.doFinal(textToEncrypt.getBytes());
return Base64.encodeToString(encrypted, Base64.DEFAULT);
}
public static String decrypt(String textToDecrypt) throws Exception {
byte[] encryted_bytes = Base64.decode(textToDecrypt, Base64.DEFAULT);
SecretKeySpec skeySpec = new SecretKeySpec(getRaw(plainText, AESSalt), "AES");
Cipher cipher = Cipher.getInstance(cypherInstance);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(initializationVector.getBytes()));
byte[] decrypted = cipher.doFinal(encryted_bytes);
return new String(decrypted, "UTF-8");
}
private static byte[] getRaw(String plainText, String salt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKeyInstance);
KeySpec spec = new PBEKeySpec(plainText.toCharArray(), salt.getBytes(), pswdIterations, keySize);
return factory.generateSecret(spec).getEncoded();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return new byte[0];
}
}
So usually i use one java file to encrypt and decrypt a string to hex with AES,
then my angular app want to consume api, that use the result of it.
this is my old java code
package decryptoor;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.Formatter;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class CryptoAndroidKoplak {
private static final String TEXT_ENCODING = "UTF-8";
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String ENCRYPTION_ALGORITM = "AES";
private static final String TAG = "Crypto";
private Cipher cipher;
private IvParameterSpec initialVector;
// private static void DEBUG(String msg){
// if(IDefines.DEBUG_LOG_TRACE){
// Log.i(TAG, msg);
// }
// }
public CryptoAndroidKoplak() {
try {
cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
initialVector = new IvParameterSpec(new byte[16]);
} catch (Exception e) {
System.out.println(e.toString());
}
}
public String encryptString(String plainText, String key) throws Exception {
return toHexString(encrypt(plainText, key)).toUpperCase();
}
public byte[] encrypt(String plainText, String key) throws Exception {
byte[] byteKey = getKeyBytes(key);
byte[] plainData = plainText.getBytes(TEXT_ENCODING);
SecretKeySpec keySpec = new SecretKeySpec(byteKey, ENCRYPTION_ALGORITM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, initialVector);
return cipher.doFinal(plainData);
}
public String decryptString(String encryptedText, String key) throws Exception {
return new String(decrypt(encryptedText, key));
}
public byte[] decrypt(String encryptedText, String key) throws Exception {
byte[] byteKey = getKeyBytes(key);
byte[] encryptData = hexToAscii(encryptedText);
SecretKeySpec keySpec = new SecretKeySpec(byteKey, ENCRYPTION_ALGORITM);
cipher.init(Cipher.DECRYPT_MODE, keySpec, initialVector);
return cipher.doFinal(encryptData);
}
public static String toMD5(String text) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] data = text.getBytes(TEXT_ENCODING);
return toHexString(md.digest(data));
}
public static String toSHA1(String text) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] data = text.getBytes(TEXT_ENCODING);
return toHexString(md.digest(data));
}
private static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
Formatter formatter = new Formatter(sb);
for (byte b : bytes) {
formatter.format("%02x", b);
}
return sb.toString();
}
private static byte[] hexToAscii(String hexStr) {
byte[] buff = new byte[hexStr.length() / 2];
int offset = 0;
for (int i = 0; i < hexStr.length(); i += 2) {
String str = hexStr.substring(i, i + 2);
buff[offset++] = (byte) Integer.parseInt(str, 16);
}
return buff;
}
private static byte[] getKeyBytes(String key) throws UnsupportedEncodingException {
byte[] keyBytes = new byte[16];
byte[] parameterKeyBytes = key.getBytes("UTF-8");
System.arraycopy(parameterKeyBytes, 0, keyBytes, 0, Math.min(parameterKeyBytes.length, keyBytes.length));
return keyBytes;
}
}
and this is my code in angular
import { Injectable } from '#angular/core';
import * as CryptoJS from 'crypto-js';
#Injectable({
providedIn: 'root'
})
export class Encryption {
constructor() {}
encryptAesToString(stringToEncrypt: string, key: string): string {
// first way
// let encrypted;
// try {
// encrypted = CryptoJS.AES.encrypt(JSON.stringify(stringToEncrypt), key);
// } catch (e) {
// console.log(e);
// }
// encrypted = CryptoJS.enc.Hex.stringify(encrypted.ciphertext);
// return encrypted;
// second way
// var b64 = CryptoJS.AES.encrypt(stringToEncrypt, key).toString();
// var e64 = CryptoJS.enc.Base64.parse(b64);
// var eHex = e64.toString(CryptoJS.enc.Hex);
// return eHex;
// third way
const key2 = CryptoJS.enc.Utf8.parse(key);
const iv = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(stringToEncrypt, key2, {
keySize: 16,
iv: iv,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
let eHex = CryptoJS.enc.Hex.stringify(encrypted.ciphertext);
return encrypted;
}
decryptAesformString(stringToDecrypt: string, key: string): string {
let decrypted: string = '';
try {
const bytes = CryptoJS.AES.decrypt(stringToDecrypt, key);
if (bytes.toString()) {
decrypted = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
}
} catch (e) {
console.log(e);
}
return decrypted;
}
}
i have try three code, the first one doesn't return hex, so i try 2 more ways but it doesn't show same encrypted string with the old java code so i cant consume the api.
any idea why this happen?
if you have better way to encrypt and decrypt with key that more simple both in angular and java, it will really help.
many thanks
after give up on how to make it same with my old java code, finally i try to make a new one hehe...
so after i read this answer, then i understand CryptoJS (library that i use in angular) implements the same key derivation function as OpenSSL. so i choose to use basic CryptoJS function to encrypt my string like this
var text = "The quick brown fox jumps over the lazy dog. 👻 👻";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
after that, what i need to do is make new java file to encrypt and decrypt aes OpenSsl, and i get what i need here in this answer. i use robert answer, cause accepted answer not really give me what i need.
but like the first answer mentioned, to encrypt and decrypt in this way, we have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy. Otherwise, AES with key size of 256 won't work and throw an exception:(you won't need JCE with up-to-date java version)
so i add some functionality to force using AES with key size of 256 without to install JCE here. note to use this, actually isnt recomended, please read the comment in ericson answer
then this is my final code to encrypt and decrypt like OpenSsl
package decryptoor;
import groovy.transform.CompileStatic;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import static java.nio.charset.StandardCharsets.*;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
/**
* Mimics the OpenSSL AES Cipher options for encrypting and decrypting messages using a shared key (aka password) with symetric ciphers.
*/
#CompileStatic
class OpenSslAes {
/** OpenSSL's magic initial bytes. */
private static final String SALTED_STR = "Salted__";
private static final byte[] SALTED_MAGIC = SALTED_STR.getBytes(US_ASCII);
static String encryptAndURLEncode(String password, String clearText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, InvalidAlgorithmParameterException, BadPaddingException, UnsupportedEncodingException {
String encrypted = encrypt(password, clearText);
return URLEncoder.encode(encrypted, UTF_8.name() );
}
/**
*
* #param password The password / key to encrypt with.
* #param data The data to encrypt
* #return A base64 encoded string containing the encrypted data.
*/
static String encrypt(String password, String clearText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, InvalidAlgorithmParameterException, BadPaddingException {
removeCryptographyRestrictions();
final byte[] pass = password.getBytes(US_ASCII);
final byte[] salt = (new SecureRandom()).generateSeed(8);
final byte[] inBytes = clearText.getBytes(UTF_8);
final byte[] passAndSalt = array_concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] data = cipher.doFinal(inBytes);
data = array_concat(array_concat(SALTED_MAGIC, salt), data);
return Base64.getEncoder().encodeToString( data );
}
/**
* #see http://stackoverflow.com/questions/32508961/java-equivalent-of-an-openssl-aes-cbc-encryption for what looks like a useful answer. The not-yet-commons-ssl also has an implementation
* #param password
* #param source The encrypted data
* #return
*/
static String decrypt(String password, String source) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
removeCryptographyRestrictions();
final byte[] pass = password.getBytes(US_ASCII);
final byte[] inBytes = Base64.getDecoder().decode(source);
final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length);
if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8);
final byte[] passAndSalt = array_concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
return new String(clear, UTF_8);
}
private static byte[] array_concat(final byte[] a, final byte[] b) {
final byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
private static void removeCryptographyRestrictions() {
if (!isRestrictedCryptography()) {
return;
}
try {
/*
* Do the following, but with reflection to bypass access checks:
*
* JceSecurity.isRestricted = false; JceSecurity.defaultPolicy.perms.clear();
* JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
*/
final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");
Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
isRestrictedField.setAccessible(true);
setFinalStatic(isRestrictedField, true);
isRestrictedField.set(null, false);
final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
defaultPolicyField.setAccessible(true);
final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);
final Field perms = cryptoPermissions.getDeclaredField("perms");
perms.setAccessible(true);
((Map<?, ?>) perms.get(defaultPolicy)).clear();
final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
instance.setAccessible(true);
defaultPolicy.add((Permission) instance.get(null));
}
catch (final Exception e) {
e.printStackTrace();
}
}
static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
private static boolean isRestrictedCryptography() {
// This simply matches the Oracle JRE, but not OpenJDK.
return "Java(TM) SE Runtime Environment".equals(System.getProperty("java.runtime.name"));
}
}
I'm writing an app which got a lot of security constraints:
It needs to store files securely encrypted, and must be able to decrypt them. Also, an Operator needs to be able to decrypt the file without the app.
To archive this, I generated a KeyPair on my PC, put the public part in my app, generate an AES SecretKey Key inside the app, encrypt and save it with my public key (for operator purposes), and put the unencrypted key in AndroidKeyStore.
To Encrypt a message, I receive the SecretKey from KeyStore, encrypt my message, get the IV I used as well as the encryptedSecretKey, and write them in a defined order to a byte array (iv->encryptedSecretKey->encryptedMessage).
To Decrypt I try the same in reverse: get the byte array, read the iv and encryptedSecretKey, and pass the rest (encryptedMessage) to my cypher to decrypt.
The problem is, that cipher.doFinal(encryptedMessage) is throwing an
javax.crypto.AEADBadTagExceptionwhich is caused by android.security.KeyStoreException: Signature/MAC verification failed.
I already checked that the encrypted message and the one I want to decrypt are exactly the same. I'm having no idea what I am doing wrong.
The class I use is the following:
package my.company.domain;
import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.support.annotation.NonNull;
import android.util.Base64;
import android.util.Log;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class CryptoHelper {
public static final String TAG = CryptoHelper.class.getSimpleName();
private static final String KEY_ALIAS = "OI1lTI1lLI1l0";
private static final char[] KEY_PASSWORD = "Il0VELI1lO".toCharArray();
private static final String PREF_NAME = "CryptoPrefs";
private static final String KEY_ENCRYPTED_SECRET = "encryptedSecret";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final int IV_SIZE = 12;
private static final int IV_BIT_LEN = IV_SIZE * 8;
//generate 128 bit key (16), other possible values 192(24), 256(32)
private static final int AES_KEY_SIZE = 16;
private static final String AES = KeyProperties.KEY_ALGORITHM_AES;
private static final String AES_MODE = AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
private static final String RSA = KeyProperties.KEY_ALGORITHM_RSA;
private static final String RSA_MODE = KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
private static final String RSA_PROVIDER = "AndroidOpenSSL";
private final Context mContext;
private final SharedPreferences mPrefs;
private SecureRandom mSecureRandom;
private KeyStore mAndroidKeyStore;
private PublicKey mPublicKey;
private byte[] mEncryptedSecretKey;
public CryptoHelper(Context context) {
mContext = context;
mSecureRandom = new SecureRandom();
mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
try {
mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
mAndroidKeyStore.load(null);
} catch (KeyStoreException e) {
Log.wtf(TAG, "Could not get AndroidKeyStore!", e);
} catch (Exception e) {
Log.wtf(TAG, "Could not load AndroidKeyStore!", e);
}
}
public void reset() throws KeyStoreException {
mAndroidKeyStore.deleteEntry(KEY_ALIAS);
}
public byte[] encrypt(byte[] message){
SecretKey secretKey = getSecretKey();
try {
Cipher cipher = Cipher.getInstance(AES_MODE);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
byte[] cryptedBytes = cipher.doFinal(message);
byte[] iv = cipher.getIV();
byte[] encryptedSecretKey = getEncryptedSecretKey();
ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
buffer
.put(iv)
.put(encryptedSecretKey)
.put(cryptedBytes);
return buffer.array();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
public byte[] encrypt(String message){
return encrypt(message.getBytes(StandardCharsets.UTF_8));
}
public byte[] decrypt(byte[] bytes){
ByteBuffer buffer = ByteBuffer.wrap(bytes);
byte[] iv = new byte[IV_SIZE];
buffer.get(iv);
byte[] unused = getEncryptedSecretKey();
buffer.get(unused);
byte[] encryptedMessage = new byte[bytes.length - IV_SIZE - unused.length];
buffer.get(encryptedMessage);
try {
Cipher cipher = Cipher.getInstance(AES_MODE);
GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), parameterSpec);
byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
return decryptedMessage;
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
public String decryptToString(byte[] bytes){
return new String(decrypt(bytes), StandardCharsets.UTF_8);
}
public byte[] decrypt(FileInputStream fileToDecrypt){
byte[] buffer = null;
try {
buffer = new byte[fileToDecrypt.available()];
fileToDecrypt.read(buffer);
buffer = decrypt(buffer);
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
public PublicKey getPublicKey() {
if (null == mPublicKey) {
mPublicKey = readPublicKey();
}
return mPublicKey;
}
public byte[] getEncryptedSecretKey() {
if (null == mEncryptedSecretKey){
mEncryptedSecretKey = Base64.decode(mPrefs.getString(KEY_ENCRYPTED_SECRET, null), Base64.NO_WRAP);
}
return mEncryptedSecretKey;
}
private void saveEncryptedSecretKey(byte[] encryptedSecretKey){
String base64EncryptedKey = Base64.encodeToString(encryptedSecretKey, Base64.NO_WRAP);
mPrefs.edit().putString(KEY_ENCRYPTED_SECRET, base64EncryptedKey).apply();
}
protected SecretKey getSecretKey(){
SecretKey secretKey = null;
try {
if (!mAndroidKeyStore.containsAlias(KEY_ALIAS)){
generateAndStoreSecureKey();
}
secretKey = (SecretKey) mAndroidKeyStore.getKey(KEY_ALIAS, KEY_PASSWORD);
} catch (KeyStoreException e) {
Log.wtf(TAG, "Could not check AndroidKeyStore alias!", e);
secretKey = null;
} catch (GeneralSecurityException e) {
e.printStackTrace();
secretKey = null;
}
return secretKey;
}
private void generateAndStoreSecureKey()
throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, KeyStoreException, BadPaddingException, IllegalBlockSizeException {
SecretKey secretKey = generateSecureRandomKey();
PublicKey publicKey = getPublicKey();
Cipher keyCipher = Cipher.getInstance(RSA_MODE, RSA_PROVIDER);
keyCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedSecretKeyBytes = keyCipher.doFinal(secretKey.getEncoded());
saveEncryptedSecretKey(encryptedSecretKeyBytes);
KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_VERIFY)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
mAndroidKeyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(secretKey), keyProtection);
}
protected PublicKey readPublicKey() {
DataInputStream dis = null;
PublicKey key = null;
try {
dis = new DataInputStream(mContext.getResources().getAssets().open("public_key.der"));
byte[] keyBytes = new byte[dis.available()];
dis.readFully(keyBytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory facotory = KeyFactory.getInstance(RSA);
key = facotory.generatePublic(spec);
} catch (Exception e) {
key = null;
} finally {
if (null != dis) {
try {
dis.close();
} catch (IOException e) {
Log.wtf(TAG, "Cannot Close Stream!", e);
}
}
}
return key;
}
#NonNull
protected SecretKey generateSecureRandomKey() {
return new SecretKeySpec(generateSecureRandomBytes(AES_KEY_SIZE), AES);
}
#NonNull
protected byte[] generateSecureRandomBytes(int byteCount) {
byte[] keyBytes = new byte[byteCount];
mSecureRandom.nextBytes(keyBytes);
return keyBytes;
}
}
And I Test it like this:
#Test
public void testCrypto() throws Exception {
CryptoHelper crypto = new CryptoHelper(InstrumentationRegistry.getTargetContext());
crypto.reset();
String verySecretOpinion = "we're all doomed";
byte[] encrypt = crypto.encrypt(verySecretOpinion);
Assert.assertNotNull("Encrypted secret is Null!", encrypt);
Assert.assertFalse("encrypted Bytes are the same as Input!", new String(encrypt, StandardCharsets.UTF_8).equals(verySecretOpinion));
String decryptedString = crypto.decryptToString(encrypt);
Assert.assertNotNull("Decrypted String must be Non-Null!", decryptedString);
Assert.assertEquals("Decrypted String doesn't equal encryption input!", verySecretOpinion, decryptedString);
}
By the way minSdkVersion is 25, so higher than Marshmallow
UPDATE:
Fixed Cipher.DECRYPT_MODE to ENCRYPT_MODE on saving the SecretKey thx to James K Polk's comment
it works If I switch from BlockMode GCM to BlockMode CBC (and changing the GCMParameterSpec to IvParamterSpec but loose the verification of the GCM Mode.
There are at least two problems with the Operator interface. First, you RSA encrypt the secret key using the wrong Cipher mode: you used DECRYPT mode when you should have used encrypt. Secondly, you are using RSA without any padding. You need to use a real padding mode, one of the OEAP padding modes is recommended.
An error in the encryption side occurs when sizing the buffer used to hold the result:
ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
allocates too much space. IV_BIT_LEN should probably be changed to IV_SIZE to get the correctly sized ByteBuffer.
The last mistake is the failure to account for the GCM authentication tag length when setting the GCMParameterSpec on the decrypt side. You initialized the tag length in this line
GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
but that's incorrect, the tag length is unrelated to the IV. Since you did not explicitly set the GCMParameterSpec on the encrypt side you got the default tag length, which happens to be 128.
You can retrieve the tag length on the encrypt side by calling
cipher.getParameters().getParameterSpec(GCMParameterSpec.class) to get the parameter spec. From this you can retrieve both the tag length and the iv. You should probably consider the tag length, 16 bytes = 128 bits, to be a hard-coded constant and not transmit it. The receiver should similar assume the tag length is 128 bits.
The code bellow takes a String and encrypts it using AES/CBC/PKCS5PADDING as transformation. I am learning as I go and I have a few questions about my code.
1. Is SecureRandom ok for generating my KEY and my IV ?
2. Whats up with all these exceptions?
4. Is my code creating any vulnerabilities in the encryption process? (mistakes maybe?)
3. Am I seeding SEcureRandom Properly?
I'm hopping to incorporate this into a larger project or build on this. Any suggestions for making the code easier to work with multiple classes?
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
public class AESCrypt {
private SecureRandom r = new SecureRandom();
private Cipher c;
private IvParameterSpec IV;
private SecretKey s_KEY;
// Constructor
public AESCrypt() throws NoSuchAlgorithmException, NoSuchPaddingException {
this.c = Cipher.getInstance("AES/CBC/PKCS5PADDING");
this.IV = generateIV();
this.s_KEY = generateKEY();
}
// COnvert the String to bytes..Should I be using UTF-8? I dont think it
// messes with the encryption and this way any pc can read it ?
// Initialize the cipher
// Encrypt the String of bytes
// Return encrypted bytes
protected byte[] encrypt(String strToEncrypt) throws InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, UnsupportedEncodingException {
byte[] byteToEncrypt = strToEncrypt.getBytes("UTF-8");
this.c.init(Cipher.ENCRYPT_MODE, this.s_KEY, this.IV, this.r);
byte[] encryptedBytes = this.c.doFinal(byteToEncrypt);
return encryptedBytes;
}
// Initialize the cipher in DECRYPT_MODE
// Decrypt and store as byte[]
// Convert to plainText and return
protected String decrypt(byte[] byteToDecrypt) throws InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException {
this.c.init(Cipher.DECRYPT_MODE, this.s_KEY, this.IV);
byte[] plainByte = this.c.doFinal(byteToDecrypt);
String plainText = new String(plainByte);
return plainText;
}
// Create the IV.
// Create a Secure Random Number Generator and an empty 16byte array. Fill
// the array.
// Returns IV
private IvParameterSpec generateIV() {
byte[] newSeed = r.generateSeed(16);
r.setSeed(newSeed);
byte[] byteIV = new byte[16];
r.nextBytes(byteIV);
IV = new IvParameterSpec(byteIV);
return IV;
}
// Create a "KeyGenerator" that takes in 'AES' as parameter
// Create a "SecureRandom" Object and use it to initialize the
// "KeyGenerator"
// keyGen.init(256, sRandom); Initialize KeyGenerator with parameters
// 256bits AES
private SecretKey generateKEY() throws NoSuchAlgorithmException {
// byte[] bytKey = AES_KEY.getBytes(); // Converts the Cipher Key to
// Byte format
// Should I use SHA-2 to get a random key or is this better?
byte[] newSeed = r.generateSeed(32);
r.setSeed(newSeed);
KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // A
// "KEyGenerator"
// object,
SecureRandom sRandom = r.getInstanceStrong(); // A "SecureRandom" object
// used to init the
// keyGenerator
keyGen.init(256, sRandom); // Initialize RAndom Number Generator
s_KEY = keyGen.generateKey();
return s_KEY;
}
public String byteArrayToString(byte[] s) {
String string = new String(s);
return string;
}
// Get Methods for all class variables
public Cipher getCipher() {
return c;
}
public IvParameterSpec getIV() {
return IV;
}
public SecretKey getSecretKey() {
return s_KEY;
}
}
Thanks to everyone who responds!
Something is funky with the following AES class I've written to do encryption and decryption. When I copy of the AES object and I choose to encrypt plain text, and then I immediately attempt to decrypt the text I just encrypted, it doesn't decrypt it fully (and does it differently every time).
e.g. I'm initializing it with a simple JSP like this:
<%#page import="com.myclass.util.AES"%>
<%
String hexMessage = "0xe800a86d90d2074fbf339aa70b6d0f62f047db15ef04c86b488a1dda3c6c4f2f2bbb444a8c709bbb4c29c7ff1f1e";
String keyText = "12345678abcdefgh";//*/
AES e = new AES();
//e.setKey(keyText);
String plaintext = "This should decode & encode!";
String ciphertext = e.encrypt(plaintext);
out.println(ciphertext);
out.println("<BR>");
out.println(e.decrypt(ciphertext));
%>
The output varies on each page load:
One time:
0x663D64E6A0AE455AB3D25D5AF2F77C72202627EBA068E6DEBE5F22C31
This should decoÁdìmèåV4ÉkÓ
Another:
0x5F5CF31961505F01EA9D5B7D7BFC656BD3117725D2EA041183F48
This s2??XêêÈ&ÀܧF?ÒDÒ?
etc:
0xC7178A34C59F74E5D68F7CE5ED655B670A0B4E715101B4DDC2122460E8
Tà#¼R×ËÖ?_U?xÎÚ?Ba?b4r!©F
The class I created is below:
package com.myclass.util;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.regex.Pattern;
import javax.crypto.*;
import javax.crypto.spec.*;
public class AES {
private static String provider = "AES/CTR/NoPadding";
private static String providerkey = "AES";
private static int size = 128;
private SecretKeySpec key;
private Cipher cipher;
private byte[] ivBytes = new byte[size/8];
private IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
public AES() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyGenerator kgen = KeyGenerator.getInstance(providerkey);
kgen.init(size); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
key = new SecretKeySpec(raw, providerkey);
cipher = Cipher.getInstance(provider);
for(int x = 0; x < (size/8); x++)
ivBytes[x] = 00;
ivSpec = new IvParameterSpec(ivBytes);
}
public void setKey(String keyText){
byte[] bText = new byte[size/8];
bText = keyText.getBytes();
key = new SecretKeySpec(bText, providerkey);
}
public void setIV(String ivText){
setIV(ivText.getBytes());
}
public void setIV(byte[] ivByte){
byte[] bText = new byte[size/8];
bText = ivByte;
ivBytes = bText;
ivSpec = new IvParameterSpec(ivBytes);
}
public String encrypt(String message) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException{
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(message.getBytes());
return byteArrayToHexString(encrypted);
}
public String decrypt(String hexCiphertext) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, UnsupportedEncodingException{
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] dec = hexStringToByteArray(hexCiphertext);
byte[] decrypted = cipher.doFinal(dec);
return new String(decrypted);
}
private static String byteArrayToHexString( byte [] raw ) {
String hex = "0x";
String s = new String(raw);
for(int x = 0; x < s.length(); x++){
char[] t = s.substring(x, x + 1).toCharArray();
hex += Integer.toHexString((int) t[0]).toUpperCase();
}
return hex;
}
private static byte[] hexStringToByteArray(String hex) {
Pattern replace = Pattern.compile("^0x");
String s = replace.matcher(hex).replaceAll("");
byte[] b = new byte[s.length() / 2];
for (int i = 0; i < b.length; i++){
int index = i * 2;
int v = Integer.parseInt(s.substring(index, index + 2), 16);
b[i] = (byte)v;
}
return b;
}
}
Based on the varied results, I'm wondering if something is getting messed up with the IV somehow, but I don't really understand why...
[EDIT] Looks like its not the IV, if I hard code that the decrypting still varies. If I hard code the key it stops varying, but still doesn't decrypt the text properly :-(.
--------------------- ===================== ---------------------
Adding the final solution I created below, based on owlstead's code and suggestions. It does the following:
1) Has a random key and iv on initialization.
2) Allows you to specify a key or iv as either a regular string, or as a hex encoded string.
3) Automatically truncates or null pads any given key or iv to make it the appropriate length.
NOTE: Item #3 could be viewed as extremely insecure since it allows you to do something stupid. For my purposes I need it, but please use with caution. If you null pad a short string for a key, your content is not going to be very secure.
--------------------- ===================== ---------------------
package com.myclass.util;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidParameterSpecException;
import java.util.regex.Pattern;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AES {
private static Charset PLAIN_TEXT_ENCODING = Charset.forName("UTF-8");
private static String CIPHER_TRANSFORMATION = "AES/CTR/NoPadding";
private static String KEY_TYPE = "AES";
private static int KEY_SIZE_BITS = 128;
private SecretKey key;
private Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
private byte[] ivBytes = new byte[KEY_SIZE_BITS/8];
public AES() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidParameterSpecException, InvalidKeyException, InvalidAlgorithmParameterException{
KeyGenerator kgen = KeyGenerator.getInstance(KEY_TYPE);
kgen.init(KEY_SIZE_BITS);
key = kgen.generateKey();
cipher.init(Cipher.ENCRYPT_MODE, key);
ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
}
public String getIVAsHex(){
return byteArrayToHexString(ivBytes);
}
public String getKeyAsHex(){
return byteArrayToHexString(key.getEncoded());
}
public void setStringToKey(String keyText){
setKey(keyText.getBytes());
}
public void setHexToKey(String hexKey){
setKey(hexStringToByteArray(hexKey));
}
private void setKey(byte[] bArray){
byte[] bText = new byte[KEY_SIZE_BITS/8];
int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
System.arraycopy(bArray, 0, bText, 0, end);
key = new SecretKeySpec(bText, KEY_TYPE);
}
public void setStringToIV(String ivText){
setIV(ivText.getBytes());
}
public void setHexToIV(String hexIV){
setIV(hexStringToByteArray(hexIV));
}
private void setIV(byte[] bArray){
byte[] bText = new byte[KEY_SIZE_BITS/8];
int end = Math.min(KEY_SIZE_BITS/8, bArray.length);
System.arraycopy(bArray, 0, bText, 0, end);
ivBytes = bText;
}
public String encrypt(String message) throws InvalidKeyException,
IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException {
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
byte[] encrypted = cipher.doFinal(message.getBytes(PLAIN_TEXT_ENCODING));
return byteArrayToHexString(encrypted);
}
public String decrypt(String hexCiphertext)
throws IllegalBlockSizeException, BadPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
UnsupportedEncodingException {
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
byte[] dec = hexStringToByteArray(hexCiphertext);
byte[] decrypted = cipher.doFinal(dec);
return new String(decrypted, PLAIN_TEXT_ENCODING);
}
private static String byteArrayToHexString(byte[] raw) {
StringBuilder sb = new StringBuilder(2 + raw.length * 2);
sb.append("0x");
for (int i = 0; i < raw.length; i++) {
sb.append(String.format("%02X", Integer.valueOf(raw[i] & 0xFF)));
}
return sb.toString();
}
private static byte[] hexStringToByteArray(String hex) {
Pattern replace = Pattern.compile("^0x");
String s = replace.matcher(hex).replaceAll("");
byte[] b = new byte[s.length() / 2];
for (int i = 0; i < b.length; i++){
int index = i * 2;
int v = Integer.parseInt(s.substring(index, index + 2), 16);
b[i] = (byte)v;
}
return b;
}
}
Rewrote, with comments inline. Funny enough, the biggest mistake was generating the hexadecimals, so I rewrote that method. It's not perfect, but I kept to your original source as much as possible.
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.regex.Pattern;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/*
* Add state handling! Don't allow same key/iv for encrypting different cipher text!
*/
public class AES {
private static Charset PLAIN_TEXT_ENCODING = Charset.forName("UTF-8");
private static String CIPHER_TRANSFORMATION = "AES/CTR/NoPadding";
private static String KEY_TYPE = "AES";
// 192 and 256 bits may not be available
private static int KEY_SIZE_BITS = 128;
private Cipher cipher;
private SecretKey key;
private IvParameterSpec iv;
static {
// only needed if the platform does not contain CTR encryption by default
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
// only needed for some platforms I presume
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
}
public AES() throws NoSuchAlgorithmException, NoSuchPaddingException,
NoSuchProviderException {
// not much use without a getter
// final KeyGenerator kgen = KeyGenerator.getInstance(KEY_TYPE);
// kgen.init(KEY_SIZE_BITS);
// key = kgen.generateKey();
cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
}
public void setKeyHex(String keyText) {
byte[] bText = hexStringToByteArray(keyText);
if (bText.length * Byte.SIZE != KEY_SIZE_BITS) {
throw new IllegalArgumentException(
"Wrong key size, expecting " + KEY_SIZE_BITS / Byte.SIZE + " bytes in hex");
}
key = new SecretKeySpec(bText, KEY_TYPE);
}
public void setIVHex(String ivText) {
byte[] bText = hexStringToByteArray(ivText);
if (bText.length != cipher.getBlockSize()) {
throw new IllegalArgumentException(
"Wrong IV size, expecting " + cipher.getBlockSize() + " bytes in hex");
}
iv = new IvParameterSpec(bText);
}
public String encrypt(String message) throws InvalidKeyException,
IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException {
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(message.getBytes(PLAIN_TEXT_ENCODING));
return byteArrayToHexString(encrypted);
}
public String decrypt(String hexCiphertext)
throws IllegalBlockSizeException, BadPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
UnsupportedEncodingException {
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] dec = hexStringToByteArray(hexCiphertext);
byte[] decrypted = cipher.doFinal(dec);
return new String(decrypted, PLAIN_TEXT_ENCODING);
}
private static String byteArrayToHexString(byte[] raw) {
StringBuilder sb = new StringBuilder(2 + raw.length * 2);
sb.append("0x");
for (int i = 0; i < raw.length; i++) {
sb.append(String.format("%02X", Integer.valueOf(raw[i] & 0xFF)));
}
return sb.toString();
}
// better add some input validation
private static byte[] hexStringToByteArray(String hex) {
Pattern replace = Pattern.compile("^0x");
String s = replace.matcher(hex).replaceAll("");
byte[] b = new byte[s.length() / 2];
for (int i = 0; i < b.length; i++) {
int index = i * 2;
int v = Integer.parseInt(s.substring(index, index + 2), 16);
b[i] = (byte) v;
}
return b;
}
public static void main(String[] args) {
try {
String key = "0x000102030405060708090A0B0C0D0E0F";
String iv = "0x000102030405060708090A0B0C0D0E0F";
String text = "Owlsteads answer";
AES aes = new AES();
aes.setKeyHex(key);
aes.setIVHex(iv);
String cipherHex = aes.encrypt(text);
System.out.println(cipherHex);
String deciphered = aes.decrypt(cipherHex);
System.out.println(deciphered);
} catch (GeneralSecurityException e) {
throw new IllegalStateException("Something is rotten in the state of Denmark", e);
} catch (UnsupportedEncodingException e) {
// not always thrown even if decryption fails, add integrity check such as MAC
throw new IllegalStateException("Decryption and/or decoding plain text message failed", e);
}
}
}
Your byteArrayToHexString method is broken. Integer.toHexString doesn't add padding zeroes but your code assumes it does. The broken conversion to hex corrupts the encrypted string, and the conversion from hex back to a raw byte string corrupts it further.