I need to encrypt & decrypt data with both Java (on Android) and SJCL (I could plausibly switch to another JS crypto library, but am familiar with SJCL so would prefer to stick with it if possible).
I have the SJCL end working fine, but at the Java end I'm not really sure what parameters I need to use to set up the key generator and cipher. The code I have so far for decryption is:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
return plaintext;
Where salt, iv and ciphertext are extracted as strings from the JSON object produced by SJCL and then decoded using a Base64 decoder to byte arrays.
Unfortunately, I have a few problems with this and the code above doesn't work.
The first problem I have is that PBKDF2WithHmacSHA256 doesn't seem to be a recognised key generation algorithm. I'm not entirely sure that this is what I want, but it appears to be right based on reading the SJCL documentation? Java does recognise PBKDF2WithHmacSHA1, but this doesn't seem to be the same algorithm SJCL implements.
Secondly, if I try using the SHA1 key algorithm, I get an error about invalid key size. Do I need to install something to enable AES with 256-bit keys? Telling the key factory to produce a 128-bit key works OK (although obviously is not compatible with SJCL, which is using a 256-bit key).
Thirdly, what cipher mode should I be using? I'm pretty sure CBC isn't right... SJCL's documentation mentions both CCM and OCB, but Java doesn't seem to support either of these out of the box -- again, do I need to install something to make this work? And which one does SJCL default to?
And finally, even if I pick parameters that make Java not complain about missing algorithms, it complains that the IV provided by decoding the SJCL output is the wrong length, which it certainly appears to be: there are 17 bytes in the resulting output, not 16 as is apparently required by AES. Do I just ignore the last byte?
I haven't tried it (in the end I switched away from using Javascript crypto in favour of using an embedded java applet with bouncycastle to handle communication), but GnuCrypto (a bouncycastle fork) supports PBKDFWithHmacSHA256. The fixed character encoding handling in SJCL presumably fixes the unexpected length of the IV (?), so this would just leave the cipher mode. From this point, it appears that the easiest approach would be to implement a relatively simple cipher mode (e.g. CTR) as an add-on for SJCL, which ought to be only a few hours work even for someone unfamiliar with the code, after which it is simply a matter of encoding and decoding the JSON-encoded data packets that are used by SJCL (which ought to be trivial).
As an alternative, it would certainly be possible to implement OCB mode for Java, despite the fact that the algorithm is proprietary, as there is a public patent grant for software distributed under the GPL (http://www.cs.ucdavis.edu/~rogaway/ocb/grant.htm).
Interestingly, I wonder whether GnuCrypto would accept a patch for OCB mode support? GnuCrypto is distributed under GPL-with-libraries-exemption, which would appear to qualify as "any version of the GNU General Public License as published by the Free Software Foundation", so theoretically at least this should be possible.
SJCL AES in java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.json.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.*;
import java.util.HashMap;
import java.util.Map;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
*
* SJCL 1.0.8
*
* dependencies:
* compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.64'
* compile group: 'org.json', name: 'json', version: '20190722'
*
* ref: https://blog.degering.name/posts/java-sjcl
*/
public class AesSJCL {
// Simply prints out the decoded string.
public static void main(String[] args) throws Exception {
String password = "password";
String plainText = "Who am I?";
// encryption
Map<String, Object> result = new AesSJCL().encrypt( password, plainText);
String json = new JSONObject(result).toString();
System.out.printf("encrypted output:\n%s\n", json);
System.out.printf("\njavascript testing code:\nsjcl.decrypt(\"%s\", '%s')\n", password, json);
// decryption
String decryptedText = new AesSJCL().decrypt(password, json);
System.out.printf("\ndecrypted output: \n%s\n", decryptedText);
}
/**
*
* #param password - password
* #param encryptedText - {"cipher":"aes","mode":"ccm","ct":"r7U/Gp2r8LVNQR7kl5qLNd8=","salt":"VwSOS3jCn6M=","v":1,"ks":128,"iter":10000,"iv":"5OEwQPtHK2ej1mHwvOf57A==","adata":"","ts":64}
* #return
* #throws Exception
*/
public String decrypt(String password, String encryptedText) throws Exception {
Decoder d = Base64.getDecoder();
// Decode the encoded JSON and create a JSON Object from it
JSONObject j = new JSONObject(new String(encryptedText));
// We need the salt, the IV and the cipher text;
// all of them need to be Base64 decoded
byte[] salt=d.decode(j.getString("salt"));
byte[] iv=d.decode(j.getString("iv"));
byte[] cipherText=d.decode(j.getString("ct"));
// Also, we need the keySize and the iteration count
int keySize = j.getInt("ks"), iterations = j.getInt("iter");
// https://github.com/bitwiseshiftleft/sjcl/blob/master/core/ccm.js#L60
int lol = 2;
if (cipherText.length >= 1<<16) lol++;
if (cipherText.length >= 1<<24) lol++;
// Cut the IV to the appropriate length, which is 15 - L
iv = Arrays.copyOf(iv, 15-lol);
// Crypto stuff.
// First, we need the secret AES key,
// which is generated from password and salt
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(),
salt, iterations, keySize);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
// Now it's time to decrypt.
Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding",
new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
// Return the final result after converting it to a string.
return new String(cipher.doFinal(cipherText));
}
/**
*
* #param password
* #param plainText
* #return
* #throws Exception
*/
public Map<String, Object> encrypt(String password, String plainText) throws Exception {
int iterations = 10000; // default in SJCL
int keySize = 128;
// https://github.com/bitwiseshiftleft/sjcl/blob/master/core/convenience.js#L321
// default salt bytes are 8 bytes
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] salt = new byte[8];
sr.nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keySize);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
// https://github.com/bitwiseshiftleft/sjcl/blob/master/core/random.js#L87
// default iv bytes are 16 bytes
SecureRandom randomSecureRandom = SecureRandom.getInstanceStrong();
byte[] iv = new byte[16];
randomSecureRandom.nextBytes(iv);
int ivl = iv.length;
if (ivl < 7) {
throw new RuntimeException("ccm: iv must be at least 7 bytes");
}
// compute the length of the length
int ol=plainText.length();
int L=2;
for (; L<4 && ( ol >>> 8*L ) > 0; L++) {}
if (L < 15 - ivl) { L = 15-ivl; }
byte[] shortIV = Arrays.copyOf(iv, 15-L);
// Now it's time to decrypt.
Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding", new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(shortIV));
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(UTF_8));
Encoder encoder = Base64.getEncoder();
Map<String, Object> map = new HashMap<>();
map.put("iv", encoder.encodeToString(iv));
map.put("iter", iterations);
map.put("ks", keySize);
map.put("salt", encoder.encodeToString(salt));
map.put("ct", encoder.encodeToString(encryptedBytes));
map.put("cipher", "aes");
map.put("mode", "ccm");
map.put("adata", "");
map.put("v", 1); // I don't know what it is.
map.put("ts", 64); // I don't know what it is.
return map;
}
}
github gist by me
ref: Java talks SJCL
You may have to use BouncyCastle to get all the cryptographic features used in SJCL. Make sure you're base64 decoding everything correctly and that SJCL doesn't add in length indicators or similar.
Related
Currently i've created a SecretKey for use in a RC4 encryption for my assignment. After the RC4 encryption i would then convert this key into string and send it to a server via UDP however when i rebuild it on the server side using SecretKeySpec, it would produce a completely different secret key.
I've looked around stackoverflow for solutions but the end would still result in a rebuilt SecretKey being different from my original SecretKey.
I've tried rebuilding the Secret Key from the String format on the client code and the result would still be a different SecretKey compared to the original so i doubt my UDP tranmission has anything to do with the result.
Below are how i went about creating the initial SecretKey for use in a RC4 encryption:
KeyGenerator keygen = KeyGenerator.getInstance("RC4");
SecretKey originalSecretKey = keygen.generateKey();
How i converted the SecretKey to String and rebuilt using SecretKeySpec:
String k = Base64.getEncoder().encodeToString(originalSecretKey.getEncoded());
byte[] decodedKey = Base64.getDecoder().decode(k);
SecretKey rebuiltSK = new SecretKeySpec(decodedKey, "RC4");
When I println "originalSecretKey" and "rebuiltSK" for checking, this is where I realised the rebuilt values are completely different and therefore i wouldnt be able to decrypt any originalSecretKey-encrypted message using the rebuiltSK.
Edit1: Silly me, thank you to "A Developer" and "Daniel" for pointing out that the actual .getEncoded() values of "originalSecretKey" and "rebuiltSK" are the same.
Apologies if I'm missing something extremely basic regarding Key generation and java's cryptography as this is my first time using them. Thank you in advance for your help !
Edit2:
Below is the code i'm currently using for my RC4 encryption and decryption:
public static byte[] encryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.ENCRYPT_MODE, k);
//Cipher.DECRYPT_MODE when on server program
byte[] encrypted = cipher.doFinal(b);
return encrypted;
}
The code above is the reason why I'm trying to rebuild the secretKey on the server end after receiving it in byte[] from the client.
I've tried running the decryption with the "rebuiltSK" as the SecretKey argument however it doesn't produce the correct plaintext although I've checked the packet.getData() on both client and server to be the same.
Your rebuilding of the SecretKey works like expected and the encryption followed by the decryption retrieves the
original plaintext.
I can only that argue (same as #Daniel) that the key was changed during transmission or the (byte[] with the)
ciphertext was not fully transmitted to the server.
The below full example code shows a complete round with key generation, encryption and decryption.
This is the result:
plaintext equals decryptedtext: true
decryptedtext: my secret
Security warning: The code below uses an UNSECURE algorithm 'RC4' or 'ARCFOUR'.
Please do not copy below code or use it in production - it is for educasional purposes only.
The code does not have any proper exception handling !
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("https://stackoverflow.com/questions/63185927/java-secretkey-to-string-and-rebuilding-back-to-secretkey-produces-different-de");
// security warning: the algorithm 'RC4' or 'ARCFOUR' is unsecure and
// should be used for educational purposes only
// do not use this code in production
// key generation
KeyGenerator keygen = KeyGenerator.getInstance("RC4");
SecretKey originalSecretKey = keygen.generateKey();
// encryption
byte[] plaintext = "my secret".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = encryptRC4(plaintext, originalSecretKey);
// decryption
String k = Base64.getEncoder().encodeToString(originalSecretKey.getEncoded());
byte[] decodedKey = Base64.getDecoder().decode(k);
SecretKey rebuiltSK = new SecretKeySpec(decodedKey, "RC4");
byte[] decryptedtext = decryptRC4(ciphertext, rebuiltSK);
// output
System.out.println("plaintext equals decryptedtext: " + Arrays.equals(plaintext, decryptedtext));
System.out.println("decryptedtext: " + new String(decryptedtext));
}
public static byte[] encryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.ENCRYPT_MODE, k);
byte[] encrypted = cipher.doFinal(b);
return encrypted;
}
public static byte[] decryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.DECRYPT_MODE, k);
byte[] decrypted = cipher.doFinal(b);
return decrypted;
}
}
I am trying to encode in nodejs and decryption for the same in nodejs works well. But when I try to do the decryption in Java using the same IV and secret, it doesn't behave as expected.
Here is the code snippet:
Encryption in nodeJs:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
_ = require('lodash'),
secret = 'd6F3231q7d1942874322a#123nab#392';
function encrypt(text, secret) {
var iv = crypto.randomBytes(16);
console.log(iv);
var cipher = crypto.createCipheriv(algorithm, new Buffer(secret),
iv);
var encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
var encrypted = encrypt("8123497494", secret);
console.log(encrypted);
And the output is:
<Buffer 94 fa a4 f4 a1 3c bf f6 d7 90 18 3f 3b db 3f b9>
94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e
Code Snippet for decryption in JAVA:
public class Test {
public static void main(String[] args) throws Exception {
String s =
"94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e";
String seed = "d6F3231q7d1942874322a#123nab#392";
decrypt(s, seed);
}
private static void decrypt(String s, String seed)
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String parts[] = s.split(":");
String ivString = parts[0];
String encodedString = parts[1];
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] secretBytes = seed.getBytes("UTF-8");
IvParameterSpec ivSpec = new IvParameterSpec(hexStringToByteArray(ivString));
/*Removed after the accepted answer
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);*/
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
cipher.init(Cipher.DECRYPT_MODE, skey, ivSpec);
byte[] output = cipher.doFinal(hexStringToByteArray(encodedString));
System.out.println(new String(output));
}
}
Output: �s˸8ƍ�
I am getting some junk value in the response. Tried a lot of options, but none of them seem to be working. Any lead/help is appreciated.
In your JS code, you're using the 32-character string d6F3231q7d19428743234#123nab#234 directly as the AES key, with each ASCII character directly mapped to a single key byte.
In the Java code, you're instead first hashing the same string with MD5, and then using the MD5 output as the AES key. It's no wonder that they won't match.
What you probably should be doing, in both cases, is either:
randomly generating a string of 32 bytes (most of which won't be printable ASCII characters) and using it as the key; or
using a key derivation function (KDF) to take an arbitrary input string and turn it into a pseudorandom AES key.
In the latter case, if the input string is likely to have less than 256 bits of entropy (e.g. if it's a user-chosen password, most of which only have a few dozen bits of entropy at best), then you should make sure to use a KDF that implements key stretching to slow down brute force guessing attacks.
Ps. To address the comments below, MD5 outputs a 16-byte digest, which will yield an AES-128 key when used as an AES SecretKeySpec. To use AES-256 in Java, you will need to provide a 32-byte key. If trying to use a 32-byte AES key in Java throws an InvalidKeyException, you are probably using an old version of Java with a limited crypto policy that does not allow encryption keys longer than 128 bits. As described this answer to the linked question, you will either need to upgrade to Java 8 update 161 or later, or obtain and install an unlimited crypto policy file for your Java version.
In the Java code you are taking the MD5 hash of secret before using it as a key:
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
Whereas, in your NodeJS code, you don't do this anywhere. So you're using two different keys when encrypting and decrypting.
Don't copy and paste code without understanding it. Especially crypto code.
Faced with the same task (but with 128, it easy to adapt for 256), here is working Java/NodeJs code with comments.
It's additionally wrapped to Base64 to readability, but it's easy to remove if you would like.
Java side (encrypt/decrypt) :
import java.lang.Math; // headers MUST be above the first class
import java.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
// one class needs to have a main() method
public class MyClass
{
private static void log(String s)
{
System.out.print("\r\n"+s);
}
public static SecureRandom IVGenerator() {
return new SecureRandom();
}
// arguments are passed using the text field below this editor
public static void main(String[] args)
{
String valueToEncrypt = "hello, stackoverflow!";
String key = "3e$C!F)H#McQfTjK";
String encrypted = "";
String decrypted = "";
//ENCODE part
SecureRandom IVGenerator = IVGenerator();
byte[] encryptionKeyRaw = key.getBytes();
//aes-128=16bit IV block size
int ivLength=16;
byte[] iv = new byte[ivLength];
//generate random vector
IVGenerator.nextBytes(iv);
try {
Cipher encryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
encryptionCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKeyRaw, "AES"), new IvParameterSpec(iv));
//encrypt
byte[] cipherText = encryptionCipher.doFinal(valueToEncrypt.getBytes());
ByteBuffer byteBuffer = ByteBuffer.allocate(ivLength + cipherText.length);
//storing IV in first part of whole message
byteBuffer.put(iv);
//store encrypted bytes
byteBuffer.put(cipherText);
//concat it to result message
byte[] cipherMessage = byteBuffer.array();
//and encrypt to base64 to get readable value
encrypted = new String(Base64.getEncoder().encode(cipherMessage));
} catch (Exception e) {
throw new IllegalStateException(e);
}
//END OF ENCODE CODE
log("encrypted and saved as Base64 : "+encrypted);
///DECRYPT CODE :
try {
//decoding from base64
byte[] cipherMessageArr = Base64.getDecoder().decode(encrypted);
//retrieving IV from message
iv = Arrays.copyOfRange(cipherMessageArr, 0, ivLength);
//retrieving encrypted value from end of message
byte[] cipherText = Arrays.copyOfRange(cipherMessageArr, ivLength, cipherMessageArr.length);
Cipher decryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKeyRaw, "AES");
decryptionCipher.init(Cipher.DECRYPT_MODE,secretKeySpec , ivSpec);
//decrypt
byte[] finalCipherText = decryptionCipher.doFinal(cipherText);
//converting to string
String finalDecryptedValue = new String(finalCipherText);
decrypted = finalDecryptedValue;
} catch (Exception e) {
throw new IllegalStateException(e);
}
log("decrypted from Base64->aes128 : "+decrypted);
//END OF DECRYPT CODE
}
}
It could be easy be tested by online java compilers (this example prepared on https://www.jdoodle.com/online-java-compiler).
NodeJs decrypt side :
const crypto = require('crypto');
const ivLength = 16;
const algorithm = 'aes-128-ctr';
const encrypt = (value, key) => {
//not implemented, but it could be done easy if you will see to decrypt
return value;
};
function decrypt(value, key) {
//from base64 to byteArray
let decodedAsBase64Value = Buffer.from(value, 'base64');
let decodedAsBase64Key = Buffer.from(key);
//get IV from message
let ivArr = decodedAsBase64Value.slice(0, ivLength);
//get crypted message from second part of message
let cipherTextArr = decodedAsBase64Value.slice(ivLength, decodedAsBase64Value.length);
let cipher = crypto.createDecipheriv(algorithm, decodedAsBase64Key, ivArr);
//decrypted value
let decrypted = cipher.update(cipherTextArr, 'binary', 'utf8');
decrypted += cipher.final('utf8');
return decrypted;
}
Background:
the application that I am working on is supposed to work offline. I have an HTML5 page and the data keyed in by the user is encrypted using crypto-js library.
And I want the encrypted message sent to java webserver and then decrypt it at the server side.
What am doing
I am able to encrypt the message using Crypto-js
<code>
var message = "my message text";
var password = "user password";
var encrypted = CryptoJS.AES.encrypt( message ,password );
console.log(encrypted.toString());
// this prints an encrypted text "D0GBMGzxKXU757RKI8hDuQ=="
</code>
What I would like to do is pass the encrypted text "D0GBMGzxKXU757RKI8hDuQ==
" to a java server side code and get the necrypted message decrypted.
I tried many options to decrypt the crypto-js encrypted message at the java server side.
Please find below my code at the server side that is supposed to do the decryption of the encrypted text.
<code>
public static String decrypt(String keyText,String encryptedText)
{
// generate key
Key key = new SecretKeySpec(keyText.getBytes(), "AES");
Cipher chiper = Cipher.getInstance("AES");
chiper.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
byte[] decValue = chiper.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
</code>
I call the java method decrypt from below code
<code>
// performs decryption
public static void main(String[] args) throws Exception
{
String decryptedText = CrypterUtil.decrypt("user password","D0GBMGzxKXU757RKI8hDuQ==");
}
</code>
But i get the following exception when i run the java decrypt code
<code>
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 13 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(AESCipher.java:372)
at javax.crypto.Cipher.passCryptoPermCheck(Cipher.java:1052)
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1010)
at javax.crypto.Cipher.implInit(Cipher.java:786)
at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
at au.gov.daff.pems.model.utils.CrypterUtil.decrypt(CrypterUtil.java:34)
at au.gov.daff.pems.model.utils.CrypterUtil.main(CrypterUtil.java:47)
Process exited with exit code 1.
</code>
Am not sure what am I doing wrong ?... What is the best way to encrypt a message using the crypto-js library so that it can be decripted else where using user keyed in password.
Thanks to Artjom B and Isaac Potoczny-Jones for the prompt response and advice. I am giving the complete solution that worked for me below for the benefit of others.
Java code to do the decryption of the cryptojs encrypted message at the Java server side
public static void main(String args[]) throws Exception{
String password = "Secret Passphrase";
String salt = "222f51f42e744981cf7ce4240eeffc3a";
String iv = "2b69947b95f3a4bb422d1475b7dc90ea";
String encrypted = "CQVXTPM2ecOuZk+9Oy7OyGJ1M6d9rW2D/00Bzn9lkkehNra65nRZUkiCgA3qlpzL";
byte[] saltBytes = hexStringToByteArray(salt);
byte[] ivBytes = hexStringToByteArray(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
SecretKeySpec sKey = (SecretKeySpec) generateKeyFromPassword(password, saltBytes);
System.out.println( decrypt( encrypted , sKey ,ivParameterSpec));
}
public static SecretKey generateKeyFromPassword(String password, byte[] saltBytes) throws GeneralSecurityException {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 100, 128);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey secretKey = keyFactory.generateSecret(keySpec);
return new SecretKeySpec(secretKey.getEncoded(), "AES");
}
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;
}
public static String decrypt(String encryptedData, SecretKeySpec sKey, IvParameterSpec ivParameterSpec) throws Exception {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, sKey, ivParameterSpec);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
The cryptojs javascript code that can do the encryption and decryption at the client side
function generateKey(){
var salt = CryptoJS.lib.WordArray.random(128/8);
var iv = CryptoJS.lib.WordArray.random(128/8);
console.log('salt '+ salt );
console.log('iv '+ iv );
var key128Bits100Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
console.log( 'key128Bits100Iterations '+ key128Bits100Iterations);
var encrypted = CryptoJS.AES.encrypt("Message", key128Bits100Iterations, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
}
function decrypt(){
var salt = CryptoJS.enc.Hex.parse("4acfedc7dc72a9003a0dd721d7642bde");
var iv = CryptoJS.enc.Hex.parse("69135769514102d0eded589ff874cacd");
var encrypted = "PU7jfTmkyvD71ZtISKFcUQ==";
var key = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
console.log( 'key '+ key);
var decrypt = CryptoJS.AES.decrypt(encrypted, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
var ddd = decrypt.toString(CryptoJS.enc.Utf8);
console.log('ddd '+ddd);
}
You have to understand that a password is not a key. A password usually goes through some hashing function to result in a bit string or byte array which is a key. It cannot be printed, so it is represented as hex or base64.
In JavaScript you use a password, but in Java you assume the same password is the key which it isn't. You could determine how CryptoJS hashes the password to arrive at the key and recreate this in Java, but it seems that it is implemented in such a way that a fresh salt is generated every time something is encrypted with a password and there is no way to change the salt.
If you really want to work will password from the user then you need to derive the key yourself. CryptoJS provides PBKDF2 for this, but it also takes a salt. You can generate one for your application and add it to the code. You would generate it this way once:
CryptoJS.lib.WordArray.random(128/8).toString();
To derive the key everytime you would pass the static salt into the password-based key derivation function (here for AES-256)
var key = CryptoJS.PBKDF2(userPassword,
CryptoJS.enc.Hex.parse(salt),
{ keySize: 256/32, iterations: 1000 });
var iv = CryptoJS.lib.WordArray.random(256/8); // random IV
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
On the server you need to convert the hex key string into a byte array. You will also need to tweak the scheme on the server from AES to AES/CBC/PKCS5Padding as it is the default in CryptoJS. Note PKCS5 and PKCS7 are the same for AES.
Also note that you will need to pass the IV from client to server and init it as
chiper.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
You can of course recreate the key from the password and the salt on the server using a Java implementation of PBKDF or just save the key for a known password and salt. You can play around with the iterations of the PBKDF what is acceptable for your users.
AES and the related algorithms can be used in many different ways, and when mixing languages, it can always be a little tricky to figure out what modes the client is using and match them to the modes of the server.
The first problem with your Java code is that you cannot use the bytes of a string as an AES key. There are lots of examples on the Internet of people doing this, but it's terribly wrong. Just like #artjom-B showed with the CryptoJS code, you need to use a "Password-based key derivation function" and it needs to also be parametrized exactly the same on the client & server.
Also, the client needs to generate salt and send it along with the crypto text; otherwise, the server cannot generate the same key from the given password. I'm not sure exactly how CryptoJS does this here's something reasonable in Java, and you can tweak the parameters as you learn how cryptoJS works:
public static SecretKey generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1000, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
}
With AES CBC, you also need to randomly generate an IV and send that along with the crypto text.
So in summary:
Figure out the AES parameters used by CryptoJS. Not sure what they are, but it sounds like: key size (256), padding (pkcs5), mode (CBC), PBE algorithm (PBKDF2), salt (random), iteration count (100)
Configure your server with the same parameters
Use a PBE key generator, along with a non-secret (but random) salt
Use AES CBC with a non-secret (but random) IV
Send the cipher text, the IV, and the salt to the server
Then on the server side, use the salt, iteration count, and the password to generate the AES key
Then base64 decode and decrypt it
I found a link in stackoverflow here use-3des-encryption-decryption-in-java,but in fact the method uses only two parameter:HG58YZ3CR9" and the "IvParameterSpec iv = new IvParameterSpec(new byte[8]);"
But the most strong option of triple des could use three different key to encrypt the message.So how to do that? I find a mehond in Cipher, which use "SecureRandom" as another parameter.So is this the right way?
The first method code is below:
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class TripleDESTest {
public static void main(String[] args) throws Exception {
String text = "kyle boon";
byte[] codedtext = new TripleDESTest().encrypt(text);
String decodedtext = new TripleDESTest().decrypt(codedtext);
System.out.println(codedtext); // this is a byte array, you'll just see a reference to an array
System.out.println(decodedtext); // This correctly shows "kyle boon"
}
public byte[] encrypt(String message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final byte[] digestOfPassword = md.digest("HG58YZ3CR9"
.getBytes("utf-8"));
final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
final byte[] plainTextBytes = message.getBytes("utf-8");
final byte[] cipherText = cipher.doFinal(plainTextBytes);
// final String encodedCipherText = new sun.misc.BASE64Encoder()
// .encode(cipherText);
return cipherText;
}
public String decrypt(byte[] message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final byte[] digestOfPassword = md.digest("HG58YZ3CR9"
.getBytes("utf-8"));
final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
final Cipher decipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
decipher.init(Cipher.DECRYPT_MODE, key, iv);
// final byte[] encData = new
// sun.misc.BASE64Decoder().decodeBuffer(message);
final byte[] plainText = decipher.doFinal(message);
return new String(plainText, "UTF-8");
}
}
As per this document, simply pass the cipher a key that is 168 bits long.
Keysize must be equal to 112 or 168.
A keysize of 112 will generate a Triple DES key with 2 intermediate keys, and a keysize of 168 will generate a Triple DES key with 3 intermediate keys.
Your code seems to do something questionable to make up for the fact that the output of MD5 is only 128 bits long.
Copy-pasting cryptographic code off the internet will not produce secure applications. Using a static IV compromises several reasons why CBC mode is better than ECB. If you are using a static key, you should probably consider generating random bytes using a secure random number generator instead of deriving the key from a short ASCII string. Also, there is absolutely no reason to use Triple DES instead of AES in new applications.
In principle, the for-next loop to generate the DES ABA key does seem correct. Note that you can provide DESede with a 16 byte key from Java 7 onwards, which amounts to the same thing.
That said, the code you've shown leaves a lot to be desired:
I is not secure:
the key is not generated by a Password Based Key Derivation Function (PBKDF) using the (password?) string
the key is composed of two keys instead of three (using a triple DES or TDEA with an ABA key)
the IV is set to all zero's instead of being randomized
the "password" string is too short
Furthermore the following code mistakes can be seen:
using new sun.misc.BASE64Encoder() which is in the Sun proprietary packages (which can be removed or changed during any upgrade of the runtime)
throwing Exception for platform exceptions and runtime exceptions (not being able to decrypt is handled the same way as not being able to instantiate the Cipher)
requesting 24 bytes instead of 16 within the Arrays.copyOf() call (which seems to return 24 SHA-1 output while there are only 20 bytes)
To generate a 3DES 24 byte (168 bits used) DES ABC key from a password (like) String you should use PBKDF-2. Adding an authentication tag is also very important if man-in-the-middle attacks or padding oracle apply. It would be much secure and much more practical to upgrade to AES if you can control the algorithms being used as well.
I'm making an app that encrypts some files. I want to use gnu's cryptix library. It says it is no longer developed since 2005, but I guess it has everything I need... should I use something else?
And I have a question about encrypting a single file. Right now I do it with a loop like this:
for(int i=0; i+block_size < bdata.length; i += block_size)
cipher.encryptBlock(bdata, i, cdata, i);
So my question is how to encrypt the last block that may not have the same size as the block_size. I was thinking maybe a should add some extra data to the last block, but than I don't know how to decrypt that...
I would strongly suggest using AES encryption and it too comes with the JAVA SDK. Have a look at: Using AES with Java Technology which will give you some great example. To read up more on AES see: Advanced Encryption Standard - Wikipedia.
Never use your own encryption scheme or an older form of an encryption scheme. AES has been tried and tested by people with far greater knowledge in that field then us, so you know it will work. Where as with your own or an old encryption scheme we might miss a fatal loop hole that will leave our data open to attacks.
See this question here to see the difference in the encryption schemes: Comparison of DES, Triple DES, AES, blowfish encryption for data
Addendum:
AES in java will work flawlessly for 192 and 256bit keys but you will have to install the newer JCE Policy Files. See here and here. You should also place the files in your JDK or else it wont work when executed from your IDE.
Note: Make sure you download the correct JCE policy files, depending on your Java version i.e 1.4, 1.5 1.6 or 7.
However if you use 128bit keys no need to install the newer JCE files.
Here is a template of some secure AES usage in java it use CBC/AES/PKCS5Padding and a random IV using RandomSecure.
Note you need both the key and IV for decrypting:
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* This program generates a AES key, retrieves its raw bytes, and then
* reinstantiates a AES key from the key bytes. The reinstantiated key is used
* to initialize a AES cipher for encryption and decryption.
*/
public class AES {
/**
* Encrypt a sample message using AES in CBC mode with a random IV genrated
* using SecyreRandom.
*
*/
public static void main(String[] args) {
try {
String message = "This string contains a secret message.";
System.out.println("Plaintext: " + message + "\n");
// generate a key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128); // To use 256 bit keys, you need the "unlimited strength" encryption policy files from Sun.
byte[] key = keygen.generateKey().getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
// build the initialization vector (randomly).
SecureRandom random = new SecureRandom();
byte iv[] = new byte[16];//generate random 16 byte IV AES is always 16bytes
random.nextBytes(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
// initialize the cipher for encrypt mode
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivspec);
System.out.println("Key: " + new String(key, "utf-8") + " This is important when decrypting");
System.out.println("IV: " + new String(iv, "utf-8") + " This is important when decrypting");
System.out.println();
// encrypt the message
byte[] encrypted = cipher.doFinal(message.getBytes());
System.out.println("Ciphertext: " + asHex(encrypted) + "\n");
// reinitialize the cipher for decryption
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec);
// decrypt the message
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Plaintext: " + new String(decrypted) + "\n");
} catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
}
/**
* Turns array of bytes into string
*
* #param buf Array of bytes to convert to hex string
* #return Generated hex string
*/
public static String asHex(byte buf[]) {
StringBuilder strbuf = new StringBuilder(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int) buf[i] & 0xff) < 0x10) {
strbuf.append("0");
}
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strbuf.toString();
}
}
I always use BouncyCastle
I also use the streaming framework instead of the for loop you were describing: it deals with the issue raised. Mostly I use that because when it comes to cryptography (and threading) I rarely trust my own code, I trust the people that live eat and breath it. Here is the code I use when I want "gash" cryptography. i.e. I have no particular threat model, and just want something "a little secure".
The hex encoding of the keys makes them much easier to manipulate / store and so on. I use "makeKey" to ... well ... make a key, then I can use the key in the encrypt and decrypt methods. You can obviously go back to using byte[] instead of hex strings for the keys.
private static boolean initialised;
private static void init() {
if (initialised)
return;
Security.addProvider(new BouncyCastleProvider());
initialised = true;
}
public static String makeKey() {
init();
KeyGenerator generator = KeyGenerator.getInstance(algorithm, provider);
generator.init(keySize);
Key key = generator.generateKey();
byte[] encoded = key.getEncoded();
return Strings.toHex(encoded);
}
public static String aesDecrypt(String hexKey, String hexCoded) {
init();
SecretKeySpec key = new SecretKeySpec(Strings.fromHex(hexKey), algorithm);
Cipher cipher = Cipher.getInstance(algorithm + "/ECB/PKCS5Padding", provider);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] codedBytes = Strings.fromHex(hexCoded);
CipherInputStream inputStream = new CipherInputStream(new ByteArrayInputStream(codedBytes), cipher);
byte[] bytes = getBytes(inputStream, 256);
String result = new String(bytes, "UTF-8");
return result;
}
public static String aesEncrypt(String hexKey, String input) {
init();
SecretKeySpec key = new SecretKeySpec(Strings.fromHex(hexKey), algorithm);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(input.length());
CipherOutputStream outputStream = new CipherOutputStream(byteArrayOutputStream, cipher);
setText(outputStream, input);
byte[] outputBytes = byteArrayOutputStream.toByteArray();
String output = new String(Strings.toHex(outputBytes));
return output;
}
public static void setText(OutputStream outputStream, String text, String encoding) {
try {
outputStream.write(text.getBytes(encoding));
outputStream.flush();
} finally {
outputStream.close();
}
}
public static byte[] getBytes(InputStream inputStream, int bufferSize) {
try {
List<ByteArrayAndLength> list = Lists.newList();
while (true) {
byte[] buffer = new byte[bufferSize];
int count = inputStream.read(buffer);
if (count == -1) {
byte[] result = new byte[ByteArrayAndLength.length(list)];
int index = 0;
for (ByteArrayAndLength byteArrayAndLength : list) {
System.arraycopy(byteArrayAndLength.bytes, 0, result, index, byteArrayAndLength.length);
index += byteArrayAndLength.length;
}
assert index == result.length;
return result;
}
list.add(new ByteArrayAndLength(buffer, count));
}
} finally {
inputStream.close();
}
}
static class ByteArrayAndLength {
byte[] bytes;
int length;
public ByteArrayAndLength(byte[] bytes, int length) {
super();
this.bytes = bytes;
this.length = length;
}
static int length(List<ByteArrayAndLength> list) {
int result = 0;
for (ByteArrayAndLength byteArrayAndLength : list) {
result += byteArrayAndLength.length;
}
return result;
}
}
I've taken out some of the exception catching to reduce the size of the code, and Strings.fromHex turns the string back into a byte[]
Maybe you should consider using a javax.crypto package.
Here is an example of how to use Ciphers:
DES encryption
Hope this helps
I would seriously think twice before going this route. The development of the software was halted because standard alternatives exist, and have a look at the mailing list, there's been no significant activity since 2009. In my book that means that the software is abandoned, and abandoned software means you're more or less on your own.
Have a look here on SO, there are several questions and answers that may help you like this one. An at first sight interesting package that could simplify things for you (but still using the standard JCE infrastructure) is jasypt