Compatible AES algorithm for Java and Javascript - java

I need to encrypt some values in Java application using AES algorithm, and decrypt the same in my Javascript module of my application.
I saw some examples over the internet but it seems there's some differences in compatibility.
like below issue :
AES encryption in javascript and decrypting in java
Can someone please point me some code examples to solve this issue.
Thanks.

AES is an exactly specified algorithm, so all AES implementations must be "compatible". Having said that, AES is a variable-key-length block-cipher, operating on 128-bit blocks. To actually use this in a piece of software, you have to make a bunch of other choices: how to deal with input consisting of more than 1 block (this is called the "mode"), in some modes you need an initialization vector, you need to deal with input not consisting of an exact number of blocks (padding), how to encode characters into bytes, and how to represent bytes in a context (like a source file) that doesn't support that. All those things need to be compatible.
Below is a tested example. It uses the standard Java crypto functions (and Apache Commons Codec), and JavaScript crypto library crypto-js. Choices are as follows: 128-bit key, cipher-block-chaining mode (which needs an initialization vector), PKCS5/7 padding, UTF-8 for character encoding, Base64 to represent byte arrays.
This piece of Java will output Base64-encoded ciphertext:
String plainText = "Hello, World! This is a Java/Javascript AES test.";
SecretKey key = new SecretKeySpec(
Base64.decodeBase64("u/Gu5posvwDsXUnV5Zaq4g=="), "AES");
AlgorithmParameterSpec iv = new IvParameterSpec(
Base64.decodeBase64("5D9r9ZVzEYYgha93/aUK2w=="));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
System.out.println(Base64.encodeBase64String(cipher.doFinal(
plainText.getBytes("UTF-8"))));
This piece of JavaScript correctly decrypts it:
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var encrypted = CryptoJS.enc.Base64.parse('3Q7r1iqtaRuJCo6QHA9/GhkTmbl4VkitV9ZsD3K2VB7LuBNg4enkJUA1cF8cHyovUH2N/jFz3kbq0QsHfPByCg==');
var key = CryptoJS.enc.Base64.parse('u/Gu5posvwDsXUnV5Zaq4g==');
var iv = CryptoJS.enc.Base64.parse('5D9r9ZVzEYYgha93/aUK2w==');
document.write(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(
{ ciphertext: encrypted },
key,
{ mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: iv, })));
</script>

Related

How to convert crypto from java to nodejs

I have following code in Java.
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(password.getBytes());
kgen.init(INIT_LENGTH, secureRandom);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(byteContent);
return result;
I need to implement it in JavaScript/Node.js
I don't know how to implement it in JavaScript from secretString to key generated by KeyGenerator
from the comment section:
This is my code:
function encodeDesCBC( textToEncode, keyString, ivString ) {
const key = Buffer.from(keyString.substring(0, 8), 'utf8')
const iv = Buffer.from(ivString.substring(0, 8), 'utf8')
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv)
let c = cipher.update(textToEncode, 'utf8', 'base64')
c += cipher.final('base64')
return base64url.escape(c)
}
My problem is secureRandom and KeyGenerator. I do not know how to implement it in nodejs
I don't know Java, but your usage looks somewhat weak, there are algorithms like PBKDF2 (which is old and discouraged now) and scrypt which do a much better job at turning human passwords into keying material. I'm also not sure where your IV is coming from in your Java code. Exactly replicating the Java code would be somewhat difficult as you'd need to know how your version of Java was implemented, and hence how the bytes passed to setSeed actually get turned into a key.
Node's Crypto module, as far as I can tell, assumes you know how long the keys are supposed to be. In the case of AES 128 in CBC mode, this would be 128 bits (i.e. 16 bytes) for both the key and IV.
Assuming you wanted to use things built into the Crypto module (argon2 would be recommended if you could relax this restriction) then you'd do something like:
const crypto = require('crypto');
const password = 'passw0rd';
const scrypt_salt = crypto.randomBytes(16);
const key = crypto.scryptSync(password, scrypt_salt, 16);
which would leave you with a suitable value in key, then you'd encrypt with:
const plaintext = 'the plain text to encode';
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
const ciphertext = Buffer.concat([
cipher.update(plaintext),
cipher.final(),
]);
and you could encode to Base64 with ciphertext.toString('base64').
Note that to be able to decrypt this, you'd need the scrypt_salt, iv, and ciphertext.
When you encrypt something using AES, there is always a "mode" in play. In your Java code you don't specify a mode explicitly - Cipher.getInstance("AES"). When you do not specify a mode, the default mode in Java is "ECB" which is the most insecured mode anyway.
In your NodeJs code, you're using "CBC" mode which is a altogether different mode.
Neither "ECB", nor "CBC" are considered secured enough. As of today, usually, the recommended mode is the GCM mode.
To generate a key from a password, ideally a "key derivation function" should be used. The 4 key derivation functions recommended by OWASP are: PBKDF2, Scrypt, Bcrypt and Argon2.
In your Java code, the password is used as a seed for the pseudo random number generator class SecureRandom. That's a little bit bizarre because even if you give the same password to your function, it will produce different key in different run. Yes, SecureRandom is also used to generate key. But if the requirement is to generate a key from a password, a key derivation function, as mentioned above, shoul be used. Both the approaches are shown in the following StackOverflow answer with detailed explanation. However, it uses "GCM" mode. But as long as you understand the concepts, you can use any mode of your choice.
https://stackoverflow.com/a/53015144/1235935
Similarly, you'll find the same implementation in NodeJs in the following StackOverflow answer:
https://stackoverflow.com/a/53573115/1235935
To further understand AES in general, you may want to go through the following StackOverflow answer:
https://stackoverflow.com/a/43779197/1235935

AES Encryption 256 ECB Mode

I tried to encrypt my plain data "hello" with key "01234567891234567890123456789012" but the problem is my encrypted code is not the same with the online reference.
Here is my android code written:
String smykey = "01234567891234567890123456789012";
String hellos = "hello";
SecretKeySpec key = new SecretKeySpec(smykey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");//("AES/ECB/PKCS7Padding");//("ECB");//("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(hellos.getBytes());
code produce,
android: 0x25 0x66...0x2d 0x87 (32 bytes)
ref: 0xa3 0xef...0x68 0x9f (16 bytes)
What's wrong with this Android code? someone I appreciate your help please?
The online reference is doing something different, the Android code produces the correct encrypted data.
The data hellos is not a multiple of the block size (16-bytes) so you must specify padding (PKCS#7).
Android is using PKCS#7 padding and 25669d21 dfd0fd6f cfef6cce 4ef12d87 is the correct result for AES with a 256-bit key, ECB mode and PKCS#7 padding.
smykey and hellos were converted to data using UTF-8 encoding.
It is a rare case where ECB mode is used, it is not secure, use CBC mode with a random iv (prepend the iv to the encrypted data). Do not return padding errors on decryption.
Use an HMAC or better yet a key extending function such as PBKDF2 to securely except a string passcode to a secure encryption key. Using a string directly as a key is not secure.

Encrypting in NetSuite Suitescript and decrypting in java application

I need to encrypt a string using SuiteScript, send it to a web service written in Java, and decrypt it there.
Using SuiteScript I'm able to encrypt and decrypt without any issue. But when I use the same key in java, I get different errors.
var x = "string to be encrypted";
var key = 'EB7CB21AA6FB33D3B1FF14BBE7DB4962';
var encrypted = nlapiEncrypt(x,'aes',key);
var decrypted = nlapiDecrypt(encrypted ,'aes',key);
^^works fine^^
The code in Java
final String strPassPhrase = "EB7CB21AA6FB33D3B1FF14BBE7DB4962"; //min 24 chars
SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
SecretKey key = factory.generateSecret(new DESedeKeySpec(strPassPhrase.getBytes()));
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE, key);
String encrypted = "3764b8140ae470bda73f7ebed3c33b0895f70c3497c85f39043345128a4bc3b3";
String decrypted = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted)));
System.out.println("Text Decryted : " + decrypted);
With the above code, I get an exception javax.crypto.BadPaddingException: Given final block not properly padded
The key was generated using openssl
openssl enc -aes-128-ecb -k mypassphrase -P
it looks like you are encrypting with AES, and decrypting with DES. I think the ciphertext needs to be decrypted with the same symmetric algorithm that you used to encrypt.
Looks like currently you have to use Suitescript to decrypt messages if it was encrypted using SuiteScript.
See suiteanswers: 35099
The workaround suggested is to use an external javascript library to encrypt/decrypt. We ended up using OpenJS on the javascript, but on the java side had to make sure the defaults were adjusted according to what is setup on the javascript side. The Java APIs were more flexible in this regard than the javascript ones.

Javascript/NodeJS equivalent code for the Java code Cipher.doFinal(byte[])?

I am migrating some server-side Java code to a new NodeJS server.
I am looking for an equivalent method call in Javascript to Java's Cipher.doFinal(byte[])
Note that I can't use NodeJS Buffers because they don't support negative Byte values. So to do my encryption, I'll need a method that accepts an array of positive and negative numbers.
Here's all of what I currently have that is related to this problem:
Node JS / Javascript:
var crypto = require('crypto');
var cipher = crypto.createCipher('aes256',key);
Java (javax.crypto.Cipher):
Cipher cipher;
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
try {
cipher = Cipher.getInstance("AES");
} catch (NoSuchAlgorithmException e) {
} catch (NoSuchPaddingException e) {
}try {
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
} catch (InvalidKeyException e) {
}
later in the Java code, I call this method where Iv represents Initialization Vector:
byte[] newIv = cipher.doFinal(myIv);
How can I get the same result in JavaScript as I do in the doFinal Java method?
Byte handling
You can use NodeJS buffers. The byte arrays in Java may just consist of signed bytes, but those bytes are not handled any differently from unsigned bytes. Only the value of the actual bits matter. If you need to treat bytes directly, it is often best to use hexadecimals instead. You can convert to an positive integer value by performing b & 0xFF and you can do the opposite by performing (byte) b.
You could of course also do something similar in NodeJS to make NodeJS handle signed numbers, but it is common to treat keys, IV's etc. as unsigned.
Cipher selection
Now for the Java AES encryption, you are using the unsafe "AES/ECB/PKCS5Padding" mode, as the Oracle Java JCE provider defaults to ECB mode of encryption and PKCS#7 padding (incorrectly named "PKCS5Padding" by Java). ECB does not use an IV, so you can ignore the value of the IV. Strangely enough, you do have to use crypto.createCipheriv(algorithm, key, iv) as the crypto.createCipher(algorithm, password) uses a password instead of a key. Of course you should also use algorithm "AES-256-ECB" for NodeJS/OpenSSL - if your key is indeed 256 bits in size.
Turns out you can place an empty IV as follows:
var cipher = require('crypto').createCipheriv('aes-256'ecb', key, '');
As for the replacement method, simply store your old IV temporarily as a new IV and then attempt to update that new IV using the old one. Here's how it would look like in NodeJS using some of the above code on Initialization Vectors created as buffers:
var newIV = oldIV.slice();
newIV = cipher.update(newIV);
oldIV = newIV;

IVParameter Spec Problem in Java

I am currently devloping a Windows application using C# and looking at developing a mobile app using Java.
The windows software and the mobile app will work together, the windows app will store information and encrypt certain information before storing it in an online database.
The mobile app will pull the information from the online database and will need to decrypt the encrypted string that is retrieved from the datbase.
The encryption method I am using in C# is below
byte[] clearTextBytes = Encoding.UTF8.GetBytes(encryptionString);
SymmetricAlgorithm rijn = SymmetricAlgorithm.Create();
MemoryStream ms = new MemoryStream();
byte[] rgbIV = Encoding.ASCII.GetBytes("ryojvlzmdalyglrj");
byte[] key = Encoding.ASCII.GetBytes("hcxilkqbbhczfeultgbskdmaunivmfuo");
CryptoStream cs = new CryptoStream(ms, rijn.CreateEncryptor(key, rgbIV), CryptoStreamMode.Write);
cs.Write(clearTextBytes, 0, clearTextBytes.Length);
cs.Close();
return Convert.ToBase64String(ms.ToArray());
The Windows method works fine.
The code I am using in Java is as follows:
KeySpec ks = new DESKeySpec("hcxilkqbbhczfeultgbskdmaunivmfuo".getBytes("UTF-8"));
SecretKey key = SecretKeyFactory.getInstance("DES").generateSecret(ks);
String ivString = "ryojvlzmdalyglrj";
byte[] ivByte = ivString.getBytes("UTF-8");
IvParameterSpec iv = new IvParameterSpec(ivByte);
//RC5ParameterSpec iv = new RC5ParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encoded = cipher.doFinal(Base64.decodeBase64("iNtaFme3B/e6DppNSp9QLg=="));
Log.d("Decoded Password", encoded.toString());
As both methods need to encrypt and decrypt the same string together it has to use the same key and IV. The only problem that I am having is in the java method the IVParameterSpec is going into the catch with an error that says IV must be 8 bytes long.
How can I resolve this to ensure that I have the same IV as C#.
Thanks for the help
The problem is that you are encrypting (in C#) with AES (also known as Rjindael), but trying to decrypt in Java with DES. If you change your Java code to use AES then it should all work fine.
DES uses an 8-byte IV because it works on 64-bit blocks. AES uses a 16-byte IV because it works on 128-bit blocks.
You should also make sure you use the same character encoding. In C# you are using ASCII, but in java you're using UTF-8. In your case they will be the same, but you should really fix it now to prevent strange bugs in future. You can change the character set name in Java to "US-ASCII" and it'll work.
You have to use the same algorithm of course. The default algorithm is for .NET is AES, so that is what you should be using on the Java side as well.

Categories