I have a rails application that encrypts (with attr_encrypted) 2 fields in one of the models.
Another part of my process, which is not the web-application needs to perform some tasks using this data (plaintext).
I'm trying to read the stored values from the DB and decrypt them but just can't..
my model looks like this:
class SecretData < ActiveRecord::Base
mysecret = "mylittlesecret"
attr_encrypted :data1, :key=>mysecret, :algorithm => "aes-256-cbc"
attr_encrypted :data2, :key=>mysecret, :algorithm => "aes-256-cbc"
...
end
The DB fields (encrypted_data1 and encrypted_data2) are filled with data but when I try to decode the base64 (attr_encrypted does that by default) and decrypt (I tried with openssl from commandline and using Java) I get "bad magic number" (openssl) or various errors about key length (in Java). I spent a lot of time trying to decrypt those strings but just couldn't find the way.
Here is all the data I have:
encrypted + base64 strings (for data1 and data2) are:
cyE3jDkKc99GVB8TiUlBxQ==
sqcbOnBTl6yy3wwjkl0qhA==
I can decode base64 from both of them and get some byte array.
When I try:
echo cyE3jDkKc99GVB8TiUlBxQ== | openssl aes-256-cbc -a -d (and type "mylittlesecret" as the password)
I get: "bad magic number"
When I try the following Java code:
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
I get "java.security.InvalidKeyException: Invalid AES key length: 14 bytes"
I've tried many variations for the Java code, so it might be that this particular one is a complete mistake..
When I try in ruby:
irb(main):069:0> Encryptor.decrypt(Base64.decode64("cyE3jDkKc99GVB8TiUlBxQ=="), ,key=>'mylittlesecret')
=> "data1-value"
I get the correct value decrypted (as you can see).
I've also noticed that when I try to encrypt the same string in Java and encode in Base64 I get a longer string (after base64). Don't know why but it's probably related..
I thought I should also have a salt/iv with the encrypted value, but I don't see it stored anywhere.. I tried to encrypt the same value twice and got the same output string so it's not a random one.
Does anyone know how does attr_encrypted (it's using ruby's Encryptor) encrypts data and how I should decrypt it externally?
Well, thanks to owlstead I was able to solve this. I'm posting the code in ruby and Java in case someone needs it in the future:
The problem, as owlstead mentioned, is indeed in the EVP_BytesToKey (key generation from a password and salt). Ruby from some reason doesn't use the standard one and therefore Java (or openssl) can't decode.
Here is a ruby implementation that uses a standard method:
def self.encrypt(options)
plaintext = options[:value]
return true if plaintext.blank?
cipher = OpenSSL::Cipher::Cipher.new(##cipher_type)
cipher.encrypt
iv = cipher.random_iv
salt = (0 ... ##salt_length).map{65.+(rand(25)).chr}.join # random salt
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(##password, salt, ##pkbdf_num_iters, cipher.key_len)
cipher.key = key
cipher.iv = iv
enc_data = cipher.update(plaintext)
enc_data << cipher.final
final_data = salt << iv << enc_data
Base64.strict_encode64(final_data)
end
def self.decrypt(options)
ciphertext = options[:value]
return true if ciphertext.blank?
cipher = OpenSSL::Cipher::Cipher.new(##cipher_type)
cipher.decrypt
cipher_data = Base64.decode64(ciphertext)
salt = cipher_data[0 .. ##salt_length-1]
iv = cipher_data[##salt_length .. ##salt_length+cipher.iv_len]
enc_data = cipher_data[##salt_length+cipher.iv_len .. -1] # the rest
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(##password, salt, ##pkbdf_num_iters, cipher.key_len)
cipher.key = key
cipher.iv = iv
plaintext = cipher.update(enc_data)
plaintext << cipher.final
plaintext
end
I've set the following parameters:
- cipher_type = aes-128-cbc (Java supports only 128 but out of the box. For more than that you need to install some additional packages)
- salt_length = 8
- pkbdf_num_iters = 1024
This is the Java method for decoding:
public String decrypt(String ciphertext) throws Exception {
byte[] crypt = Base64.decodeBase64(ciphertext);
// parse the encrypted data and get salt and IV
byte[] salt = Arrays.copyOfRange(crypt, 0, saltLength);
byte[] iv = Arrays.copyOfRange(crypt, saltLength, saltLength + ivLength);
byte[] encryptedData = Arrays.copyOfRange(crypt, saltLength + ivLength, crypt.length);
// generate key from salt and password
SecretKeyFactory f = SecretKeyFactory.getInstance(secretKeyName);
KeySpec ks = new PBEKeySpec(password.toCharArray(), salt, pbkdfNumIters, keyLength);
SecretKey s = f.generateSecret(ks);
Key keySpec = new SecretKeySpec(s.getEncoded(),"AES");
// initialize the cipher object with the key and IV
Cipher cipher = Cipher.getInstance(cipherAlgo);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// decrypt
byte[] decBytes = cipher.doFinal(encryptedData);
return new String(decBytes);
}
Worked for me.
Hope it helps (or will, to someone..)
Zach
You will need -nosalt to decrypt the data with OpenSSL. For Java you will need an implementation of the OpenSSL EVP_BytesToKey method. One implementation can be found on the blog of Ola Bini. Thanks for putting this in the public domain, Ola.
public static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md,
byte[] salt, byte[] data, int count) {
byte[][] both = new byte[2][];
byte[] key = new byte[key_len];
int key_ix = 0;
byte[] iv = new byte[iv_len];
int iv_ix = 0;
both[0] = key;
both[1] = iv;
byte[] md_buf = null;
int nkey = key_len;
int niv = iv_len;
int i = 0;
if (data == null) {
return both;
}
int addmd = 0;
for (;;) {
md.reset();
if (addmd++ > 0) {
md.update(md_buf);
}
md.update(data);
if (null != salt) {
md.update(salt, 0, 8);
}
md_buf = md.digest();
for (i = 1; i < count; i++) {
md.reset();
md.update(md_buf);
md_buf = md.digest();
}
i = 0;
if (nkey > 0) {
for (;;) {
if (nkey == 0)
break;
if (i == md_buf.length)
break;
key[key_ix++] = md_buf[i];
nkey--;
i++;
}
}
if (niv > 0 && i != md_buf.length) {
for (;;) {
if (niv == 0)
break;
if (i == md_buf.length)
break;
iv[iv_ix++] = md_buf[i];
niv--;
i++;
}
}
if (nkey == 0 && niv == 0) {
break;
}
}
for (i = 0; i < md_buf.length; i++) {
md_buf[i] = 0;
}
return both;
}
Related
I was given this .NET code and I need to convert it to Java. I have found a bunch of articles but nothing I try works.
public string EncryptNVal(string vVal, string accountNumber, string sharedSecret)
{
byte[] IV = Convert.FromBase64String(vVal);
byte[] Key = Convert.FromBase64String(sharedSecret);
SymmetricAlgorithm sa = Rijndael.Create();
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, sa.CreateEncryptor(Key, IV),
CryptoStreamMode.Write))
{
byte[] data = Encoding.UTF8.GetBytes(accountNumber);
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
ms.Position = 0;
return Convert.ToBase64String(ms.ToArray());
}
}
This is some of my attempt which you can guess throws exceptions.
public String EncryptNVal(String vVal, String accountNumber, String sharedSecret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] IV = Base64.decodeBase64(vVal);
byte[] Key =Base64.decodeBase64(sharedSecret);
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(Base64.decodeBase64(sharedSecret).toString().toCharArray());
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = factory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(IV,salt,1000);
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] ciphertext = cipher.doFinal(accountNumber.getBytes(StandardCharsets.UTF_8));
return Base64.encodeBase64String(ciphertext);
In the same project I have this code which I think I got correct, but any confirmation on that would be helpful as well.
.NET code I was given
public string GetMd5Hash(string input)
{
using (var md5Hash = MD5.Create())
{
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
var sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
foreach (var t in data)
{
sBuilder.Append(t.ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
}
What I wrote in Java:
public String GetHash(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] inputBytes = input.getBytes("UTF-8");
md.update(inputBytes);
byte inputHash[] = md.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < inputHash.length; i++) {
hexString.append(Integer.toHexString(0xFF & inputHash[i]));
}
return hexString.toString();
}
On a side note I can use either MD5 or SHA256, the .NET code was using MD5, so I was trying to follow that since my knowledge of encryption is about null. I am willing to use the SHA256 if someone can give me good advice.
I took me some time, and a lot of errors to figure this out, but here's a java method which yields exactly the same output as your C# method.
I'm no expert at all in crypto stuff, but this seems to do the work.
public static String EncryptNVal(String vVal, String accountNumber, String sharedSecret){
try {
byte[] vValAsBytes = java.util.Base64.getDecoder().decode(vVal);
byte[] sharedSecretAsBytes = java.util.Base64.getDecoder().decode(sharedSecret);
byte[] accountNumberAsBytes = accountNumber.getBytes();
SecretKeySpec key = new SecretKeySpec(sharedSecretAsBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(vValAsBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(accountNumberAsBytes);
String signature = java.util.Base64.getEncoder().encodeToString(output);
return signature;
}
catch(Exception e){
e.printStackTrace();
return "<dummy>";
}
}
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
A few additional info :
Rijndael is not AES, in the sense that it's not implementing the actual AES standard. If this is for production use, I'd suggest moving to actual AES (take a look at this link for info).
AES/CBC/PKCS5Padding is the closest match I could find for C#'s Rijndael . Although C# seems to use PKCS7, this doesn't seem available for Java (at least not on my system). PKCS5Padding yields the same result, but it will probably not work in 100% of the cases.
It only required trial and error, no deep knowledge of either C# or Java. You'll probably face similar challenges (or related problems which will be induced by your particular use case), and I'd suggest you apply a similar workflow as mine, when it comes to translating from a language to another :
Use and abuse of printing capabilities, to ensure all steps of both codes have the same state
Arrange the code with the same logical flow on both sides
Thoroughly read the documentation of your source function that you want to translate
I am given a Rijndael .Net encrypted file and .Net RSA XML Key and asked to decrypt it in Java.
The key provided to me is 256 bit.
I have parsed the RSA XML file and generated the public Key in Java. I tried to decrypt using the generated key, however I am getting the exception Illegal Key Size, I think I am doing something wrong in my Java code.
Can any one please help to check if anything is wrong with my code?
.Net encryption code:
public static void EncryptFile(string fileIn, string fileOut,
string publicKeyName, string publicKeyFile)
{
try
{
// Read the public key from key file
StreamReader sr = new StreamReader(publicKeyFile);
string strKeyText = sr.ReadToEnd();
sr.Close();
//Initialize Key container and Crypto service provider
RSACryptoServiceProvider rsa;
CspParameters cspp = new CspParameters();
cspp.KeyContainerName = publicKeyName;
rsa = new RSACryptoServiceProvider(cspp);
rsa.FromXmlString(strKeyText);
rsa.PersistKeyInCsp = true;
// Create instance of Rijndael for
// symetric encryption of the data.
RijndaelManaged alg = new RijndaelManaged();
// Key size is set to 256 for strong encryption
alg.KeySize = 256;
alg.BlockSize = 256;
// Cipher Mode is set to CBC to process the file in chunks
alg.Mode = CipherMode.CBC;
// Set padding mode to process the last block of the file
alg.Padding = PaddingMode.ISO10126;
ICryptoTransform transform = alg.CreateEncryptor();
// Use RSACryptoServiceProvider to
// enrypt the Rijndael key.
byte[] KeyEncrypted = rsa.Encrypt(alg.Key, false);
// Create byte arrays to contain
// the length values of the key and IV.
int intKeyLength = KeyEncrypted.Length;
byte[] LenK = BitConverter.GetBytes(intKeyLength);
int intIVLength = alg.IV.Length;
byte[] LenIV = BitConverter.GetBytes(intIVLength);
using (FileStream fsOut = new FileStream(fileOut, FileMode.Create))
{
// Write the following to the FileStream
// for the encrypted file (fsOut):
// - length of the key
// - length of the IV
// - ecrypted key
// - the IV
// - the encrypted cipher content
fsOut.Write(LenK, 0, 4);
fsOut.Write(LenIV, 0, 4);
fsOut.Write(KeyEncrypted, 0, intKeyLength);
fsOut.Write(alg.IV, 0, intIVLength);
// Now write the cipher text using
// a CryptoStream for encrypting.
using (CryptoStream cs = new CryptoStream(fsOut, transform, CryptoStreamMode.Write))
{
// intBlockSizeBytes can be any arbitrary size.
int intBlockSizeBytes = alg.BlockSize / 8;
byte[] DataBytes = new byte[intBlockSizeBytes];
int intBytesRead = 0;
using (FileStream fsIn = new FileStream(fileIn, FileMode.Open))
{
// By encrypting a chunk at
// a time, you can save memory
// and accommodate large files.
int intCount;
int intOffset = 0;
do
{
// if last block size is less than encryption chunk size
// use the last block size and padding character is used
// for remaining bytes
if (intBlockSizeBytes > (fsIn.Length - fsIn.Position))
{
intBlockSizeBytes = ((int)(fsIn.Length - fsIn.Position));
DataBytes = new byte[intBlockSizeBytes];
}
// read data bytes
intCount = fsIn.Read(DataBytes, 0, intBlockSizeBytes);
intOffset += intCount;
// write it into crypto stream
cs.Write(DataBytes, 0, intCount);
intBytesRead += intBlockSizeBytes;
} while (intCount > 0);
// close input file
fsIn.Close();
}
// close crypto stream
cs.FlushFinalBlock();
cs.Close();
}
// close output file
fsOut.Close();
}
}
catch
{
throw;
}
}
Java Code that I wrote to decrypt it:
byte[] expBytes = Base64.decodeBase64(pkey.getExponentEle().trim());
byte[] modBytes = Base64.decodeBase64(pkey.getModulusEle().trim());
byte[] dBytes = Base64.decodeBase64(pkey.getdEle().trim());
BigInteger modules = new BigInteger(1, modBytes);
BigInteger exponent = new BigInteger(1, expBytes);
BigInteger d = new BigInteger(1, dBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(modules, exponent);
PublicKey pubKey = factory.generatePublic(pubSpec);
final byte[] keyData = Arrays.copyOf(pubKey.getEncoded(), 256
/ Byte.SIZE);
final byte[] ivBytes = Arrays.copyOf(keyData, cipher.getBlockSize());
AlgorithmParameterSpec paramSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyData, "AES"), paramSpec);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("decrypted: " + new String(decrypted));
If I change cipher initialization to cipher.init(Cipher.DECRYPT_MODE, pubKey);, then I am getting the error Invalid AES key length: 162 bytes
You are using the public key wrong way. Do you really understad how the C# program works? what parameters is it using?
You are just using the public key bits as the AES key (even I don't realy understand how do you get 162 bytes from it).
This is example of "hybrid encryption" - the data themselves are encrypted by a random AES key (in this you claim it's 256 bit) and the AES key (in this case the IV too) is encrypted by the RSA public key. In Java there are many examples how to do that.
Even to decrypt the AES key you should know parameters used to encrypt it (RSA/ECB/PKCS5Padding, RSA-AOEP, ...), though it should be inside the XML.
Comming to the parameters - you are using PKCS5Padding, but check the .NET code, it's different
The encrypted text is done in JAVA (which we have no JAVA background at all)
The decryption will be in C#, and here is the code
public static string DecryptString(string Message, string Passphrase)
{
byte[] Results;
UTF8Encoding UTF8 = new UTF8Encoding();
MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
// byte[] TDESKey = UTF8.GetBytes(Passphrase);
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();
TDESAlgorithm.Key = TDESKey;
// TDESAlgorithm.Mode = CipherMode.CTS;
TDESAlgorithm.Padding = PaddingMode.Zeros;
byte[] DataToDecrypt = Convert.FromBase64String(Message);
try
{
ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
}
finally
{
TDESAlgorithm.Clear();
HashProvider.Clear();
}
return Encoding.UTF8.GetString(Results);
}
Encrypted Java code is
public String encryptData(String privateKey, String rawData)
{
Cipher cipher = null;
try
{
cipher = Cipher.getInstance(DESEDE_ENCRYPTION_SCHEME);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(privateKey));
byte[] plainText = rawData.getBytes(UNICODE_FORMAT);
byte[] encryptedText = cipher.doFinal(plainText);
return new String(Base64.encodeBase64(encryptedText));
}
}
However, when tried to decrypt, got the error message: BAD DATA
Where am I missing here?
You are not using MD5 in Java, so you should not be using it in your .NET for computing the hash.
Your key should have been generated using a specific encoding and same you should use in .NET.
Please note, there is some fundamental difference in java KeySpec and the Key being used for TripleDESCryptoServiceProvider. As mentioned by Microsfot https://msdn.microsoft.com/en-us/library/system.security.cryptography.tripledescryptoserviceprovider.aspx
Triple DES only supports "key lengths from 128 bits to 192 bits in increments of 64 bits"
So you need to convert your key appropriately before assigning. To do this you can use the Array.Resize method as following.
byte[] TDESKey = Encoding.UTF8.GetBytes(Passphrase);
System.Array.Resize(ref TDESKey , 192 / 8);
Hope this will help.
I'm expanding an iOS project over to Android. My existing application communicates with a server via PHP using an AES encryption system.
Here are the functions that I am using on the PHP side:
Encrypt
function cryptAESEncrypt($string,$key) {
$key = md5($key);
$iv = "1234567890123436"; //IV isn't needed if MCRYPT_MODE is ECB (What we are using)
$data = $data = base64_encode($string);
$algorythm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_ECB;
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$data,MCRYPT_MODE_ECB,$iv);
return base64_encode($encrypted);
}
Decrypt
function cryptAESDecrypt($string,$key) {
$key = md5($key);
$iv = "1234567890123436"; //IV isn't needed if MCRYPT_MODE is ECB (What we are using)
$data = base64_decode($string);
$algorythm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_ECB;
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$key,$data,MCRYPT_MODE_ECB,$iv);
return base64_decode($decrypted);
}
The general flow of the process is:
md5 hash the $key (brings it down to 16 characters regardless)
Base64 Encode the $string
Encrypt the Base64'ed using 128Bit AES/RIJNDAEL in ECB mode (no IV)
Base64 the encrypted data and returns it as a string.
The decryption works the same but in reverse.
Now I'm just playing with samples but don't seem to be having much luck. I've encrypted the string "test" in PHP using that function ("test" was the key too - MD5'ed to 098f6bcd4621d373cade4e832627b4f6) and I am given the output of "ijzLe/2WgbaP+n3YScQSgQ==".
Now what I tried in Java didn't work as I get an incorrect key length error but I had more luck with a previous snippet earlier. Here's what I had anyway:
String key = "test";
String in = "ijzLe/2WgbaP+n3YScQSgQ==";
SecretKeySpec skeySpec = new SecretKeySpec(md5(key).getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encryptedByteArray = Base64.decode(in.getBytes(),0);
byte[] decryptedByteArray = cipher.doFinal(encryptedByteArray);
String decryptedData = new String(Base64.decode(decryptedByteArray, 0));
Log.v("NOTE","Data: "+decryptedData);
As I said though, that doesn't work. Now my question is, is there anybody that can help me make my Java code work with the supplied PHP code as I can't change that (had other code working using different PHP snippets).
Thanks to Duncan in the comments I found out the issue was with my MD5 hash function..
Found a working version for reference:
public String md5(String s) {
if (s != null)
{
try { // Create MD5 Hash
MessageDigest digest = java.security.MessageDigest .getInstance("MD5");
digest.update(s.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
String h = Integer.toHexString(0xFF & messageDigest[i]);
while (h.length() < 2)
h = "0" + h;
hexString.append(h);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
return "";
}
I am generating a key and need to store it in DB, so I convert it into a String, but to get back the key from the String. What are the possible ways of accomplishing this?
My code is,
SecretKey key = KeyGenerator.getInstance("AES").generateKey();
String stringKey=key.toString();
System.out.println(stringKey);
How can I get the key back from the String?
You can convert the SecretKey to a byte array (byte[]), then Base64 encode that to a String. To convert back to a SecretKey, Base64 decode the String and use it in a SecretKeySpec to rebuild your original SecretKey.
For Java 8
SecretKey to String:
// create new key
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
// get base64 encoded version of the key
String encodedKey = Base64.getEncoder().encodeToString(secretKey.getEncoded());
String to SecretKey:
// decode the base64 encoded string
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
// rebuild key using SecretKeySpec
SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
For Java 7 and before (including Android):
NOTE I: you can skip the Base64 encoding/decoding part and just store the byte[] in SQLite. That said, performing Base64 encoding/decoding is not an expensive operation and you can store strings in almost any DB without issues.
NOTE II: Earlier Java versions do not include a Base64 in one of the java.lang or java.util packages. It is however possible to use codecs from Apache Commons Codec, Bouncy Castle or Guava.
SecretKey to String:
// CREATE NEW KEY
// GET ENCODED VERSION OF KEY (THIS CAN BE STORED IN A DB)
SecretKey secretKey;
String stringKey;
try {secretKey = KeyGenerator.getInstance("AES").generateKey();}
catch (NoSuchAlgorithmException e) {/* LOG YOUR EXCEPTION */}
if (secretKey != null) {stringKey = Base64.encodeToString(secretKey.getEncoded(), Base64.DEFAULT)}
String to SecretKey:
// DECODE YOUR BASE64 STRING
// REBUILD KEY USING SecretKeySpec
byte[] encodedKey = Base64.decode(stringKey, Base64.DEFAULT);
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
To show how much fun it is to create some functions that are fail fast I've written the following 3 functions.
One creates an AES key, one encodes it and one decodes it back. These three methods can be used with Java 8 (without dependence of internal classes or outside dependencies):
public static SecretKey generateAESKey(int keysize)
throws InvalidParameterException {
try {
if (Cipher.getMaxAllowedKeyLength("AES") < keysize) {
// this may be an issue if unlimited crypto is not installed
throw new InvalidParameterException("Key size of " + keysize
+ " not supported in this runtime");
}
final KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(keysize);
return keyGen.generateKey();
} catch (final NoSuchAlgorithmException e) {
// AES functionality is a requirement for any Java SE runtime
throw new IllegalStateException(
"AES should always be present in a Java SE runtime", e);
}
}
public static SecretKey decodeBase64ToAESKey(final String encodedKey)
throws IllegalArgumentException {
try {
// throws IllegalArgumentException - if src is not in valid Base64
// scheme
final byte[] keyData = Base64.getDecoder().decode(encodedKey);
final int keysize = keyData.length * Byte.SIZE;
// this should be checked by a SecretKeyFactory, but that doesn't exist for AES
switch (keysize) {
case 128:
case 192:
case 256:
break;
default:
throw new IllegalArgumentException("Invalid key size for AES: " + keysize);
}
if (Cipher.getMaxAllowedKeyLength("AES") < keysize) {
// this may be an issue if unlimited crypto is not installed
throw new IllegalArgumentException("Key size of " + keysize
+ " not supported in this runtime");
}
// throws IllegalArgumentException - if key is empty
final SecretKeySpec aesKey = new SecretKeySpec(keyData, "AES");
return aesKey;
} catch (final NoSuchAlgorithmException e) {
// AES functionality is a requirement for any Java SE runtime
throw new IllegalStateException(
"AES should always be present in a Java SE runtime", e);
}
}
public static String encodeAESKeyToBase64(final SecretKey aesKey)
throws IllegalArgumentException {
if (!aesKey.getAlgorithm().equalsIgnoreCase("AES")) {
throw new IllegalArgumentException("Not an AES key");
}
final byte[] keyData = aesKey.getEncoded();
final String encodedKey = Base64.getEncoder().encodeToString(keyData);
return encodedKey;
}
Actually what Luis proposed did not work for me. I had to figure out another way. This is what helped me. Might help you too.
Links:
*.getEncoded(): https://docs.oracle.com/javase/7/docs/api/java/security/Key.html
Encoder information: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Encoder.html
Decoder information: https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Decoder.html
Code snippets:
For encoding:
String temp = new String(Base64.getEncoder().encode(key.getEncoded()));
For decoding:
byte[] encodedKey = Base64.getDecoder().decode(temp);
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "DES");
You don't want to use .toString().
Notice that SecretKey inherits from java.security.Key, which itself inherits from Serializable. So the key here (no pun intended) is to serialize the key into a ByteArrayOutputStream, get the byte[] array and store it into the db. The reverse process would be to get the byte[] array off the db, create a ByteArrayInputStream offf the byte[] array, and deserialize the SecretKey off it...
... or even simpler, just use the .getEncoded() method inherited from java.security.Key (which is a parent interface of SecretKey). This method returns the encoded byte[] array off Key/SecretKey, which you can store or retrieve from the database.
This is all assuming your SecretKey implementation supports encoding. Otherwise, getEncoded() will return null.
edit:
You should look at the Key/SecretKey javadocs (available right at the start of a google page):
http://download.oracle.com/javase/6/docs/api/java/security/Key.html
Or this from CodeRanch (also found with the same google search):
http://www.coderanch.com/t/429127/java/java/Convertion-between-SecretKey-String-or
try this, it's work without Base64 ( that is included only in JDK 1.8 ), this code run also in the previous java version :)
private static String SK = "Secret Key in HEX";
// To Encrupt
public static String encrypt( String Message ) throws Exception{
byte[] KeyByte = hexStringToByteArray( SK);
SecretKey k = new SecretKeySpec(KeyByte, 0, KeyByte.length, "DES");
Cipher c = Cipher.getInstance("DES","SunJCE");
c.init(1, k);
byte mes_encrypted[] = cipher.doFinal(Message.getBytes());
String MessageEncrypted = byteArrayToHexString(mes_encrypted);
return MessageEncrypted;
}
// To Decrypt
public static String decrypt( String MessageEncrypted )throws Exception{
byte[] KeyByte = hexStringToByteArray( SK );
SecretKey k = new SecretKeySpec(KeyByte, 0, KeyByte.length, "DES");
Cipher dcr = Cipher.getInstance("DES","SunJCE");
dc.init(Cipher.DECRYPT_MODE, k);
byte[] MesByte = hexStringToByteArray( MessageEncrypted );
byte mes_decrypted[] = dcipher.doFinal( MesByte );
String MessageDecrypeted = new String(mes_decrypted);
return MessageDecrypeted;
}
public static String byteArrayToHexString(byte bytes[]){
StringBuffer hexDump = new StringBuffer();
for(int i = 0; i < bytes.length; i++){
if(bytes[i] < 0)
{
hexDump.append(getDoubleHexValue(Integer.toHexString(256 - Math.abs(bytes[i]))).toUpperCase());
}else
{
hexDump.append(getDoubleHexValue(Integer.toHexString(bytes[i])).toUpperCase());
}
return hexDump.toString();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2)
{
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));
}
return data;
}
Converting SecretKeySpec to String and vice-versa:
you can use getEncoded() method in SecretKeySpec which will give byteArray, from that you can use encodeToString() to get string value of SecretKeySpec in Base64 object.
While converting SecretKeySpec to String: use decode() in Base64 will give byteArray, from that you can create instance for SecretKeySpec with the params as the byteArray to reproduce your SecretKeySpec.
String mAesKey_string;
SecretKeySpec mAesKey= new SecretKeySpec(secretKey.getEncoded(), "AES");
//SecretKeySpec to String
byte[] byteaes=mAesKey.getEncoded();
mAesKey_string=Base64.encodeToString(byteaes,Base64.NO_WRAP);
//String to SecretKeySpec
byte[] aesByte = Base64.decode(mAesKey_string, Base64.NO_WRAP);
mAesKey= new SecretKeySpec(aesByte, "AES");