I need to decrypt a hex message in JavaScript that has the exact same outcome as the code written in Java. However the Javascript version using CryptoJs returns an empty result
Code in Java:
private static void create()
{
byte[] sessionKey = fromHexString("dae25b4defd646cd99b7b95d450d6646");
byte[] data = fromHexString("2700012e27999bdaa6b0530375be269985a0238e5e4baf1528ebaf34a8e5e8c13a58b25bcb82514ee6c86c02ff77ac52bdbd88");
byte[] payload_data = new byte[48];
byte[] decrypted_data = new byte[48];
for(int i=0;i<48;i++) {
payload_data[i]= data[3+i];
}
try{
SecretKeySpec skeySpec = new SecretKeySpec(sessionKey, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
decrypted_data = cipher.doFinal(payload_data);
}catch(Exception e){
System.out.println(e);
}
String my_data = byteArrayToHex(decrypted_data);
System.out.println(my_data);
}
private static String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02X", b));
return sb.toString();
}
private static byte[] fromHexString(String src) {
byte[] biBytes = new BigInteger("10" + src.replaceAll("\\s", ""), 16).toByteArray();
return Arrays.copyOfRange(biBytes, 1, biBytes.length);
}
which returns a result of: "248A8143837F51E03C3522934DD47C38612C90EC57D79D7DE6174EAC85B75F9ADCD7D6686EBF4B9F2E9FE441D373E69E"
Code in JavaScript:
import * as cryptojs from 'crypto-js';
export function create() {
const sessionKey = Buffer.from('dae25b4defd646cd99b7b95d450d6646', 'hex');
const data = Buffer.from('2700012e27999bdaa6b0530375be269985a0238e5e4baf1528ebaf34a8e5e8c13a58b25bcb82514ee6c86c02ff77ac52bdbd88', 'hex');
const payloadData = Buffer.alloc(48);
for (let i = 0; i < 48; i += 1) {
payloadData[i] = data[3 + i];
}
const decrypted = cryptojs.AES.decrypt(
cryptojs.enc.Hex.parse(toHexString(payloadData)),
cryptojs.enc.Hex.parse(toHexString(sessionKey)),
{
mode: cryptojs.mode.ECB,
padding: cryptojs.pad.NoPadding,
}
).toString(cryptojs.enc.Hex);
console.log({
decrypted,
});
}
function toHexString(byteArray) {
// eslint-disable-next-line no-bitwise
return Array.prototype.map.call(byteArray, byte => `0${(byte & 0xff).toString(16)}`.slice(-2)).join('');
}
result:
{ decrypted: '' }
Any idea on what might be wrong ?
The decryption with CryptoJS could look as follows:
function decrypt() {
var sessionKey = 'dae25b4defd646cd99b7b95d450d6646';
var data = '2700012e27999bdaa6b0530375be269985a0238e5e4baf1528ebaf34a8e5e8c13a58b25bcb82514ee6c86c02ff77ac52bdbd88';
var payload_data = data.substr(6);
var decrypted = CryptoJS.AES.decrypt(
payload_data,
CryptoJS.enc.Hex.parse(sessionKey),
{
format: CryptoJS.format.Hex,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.NoPadding,
}
).toString(CryptoJS.enc.Hex);
console.log(decrypted.replace(/(.{48})/g,'$1\n'));
}
decrypt();
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Update: Regarding your comment: The issue in your code is that the ciphertext is passed as WordArray. The following two changes are one way to make it work:
...
const decrypted = cryptojs.AES.decrypt(
toHexString(payloadData), // pass the ciphertext as hex encoded string
cryptojs.enc.Hex.parse(toHexString(sessionKey)),
{
format: cryptojs.format.Hex, // specify the encoding of the ciphertext
mode: cryptojs.mode.ECB,
...
cryptojs.AES.decrypt() expects the ciphertext in a CipherParams object (and not simply in a WordArray). Alternatively the ciphertext can be passed Base64 encoded or in another encoding that must be specified explicitly with the format parameter (e.g. hexadecimal here, Base64 is the default). The ciphertext is then implicitly converted into a CipherParams object, see here.
But please consider: Since all conversions can be done with CryptoJS onboard means, helpers like toHexString() are not really necessary. For this there are especially the encoder classes, see here. The same applies to the NodeJS Buffer. It makes more sense to work with WordArrays, because they are processed directly by CryptoJS.
Related
I have written the following Java code to encrypt a message/data. Currently it is using default encryption algorithm (AES/ECB/PKCS5PADDING). In JavaScript while decrypting I have used mode ECB. I read articles that ECB is not secure. So I need to move to CBC mode. But changing the mode is causing issue for me. Can you help me to change the mode in proper way so that it is compatible?
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class EncryptDecryptImpl {
private static final String secretKey = "abcdefghijklmnop";
private static final String mySecretKey = "my-secret-key";
private static final String encryptionAlgorithm = "AES"; // need to use AES/CBC/PKCS5Padding
public static String encrypt(String data, String secret) {
try {
Key key = generateKey(secret);
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedValue = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedValue);
} catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException |
BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
}
return null;
}
public static String decrypt(String strToDecrypt, String secret) {
try {
Key key = generateKey(secret);
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException |
BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
}
return null;
}
private static Key generateKey(String secret) {
byte[] decoded = Base64.getDecoder().decode(secret.getBytes());
return new SecretKeySpec(decoded, encryptionAlgorithm);
}
public static String encodeKey(String key) {
byte[] encoded = Base64.getEncoder().encode(key.getBytes());
return new String(encoded);
}
public static String decodeKey(String key) {
byte[] decoded = Base64.getDecoder().decode(key.getBytes());
return new String(decoded);
}
public static String encodedBase64Key() {
return encodeKey(secretKey);
}
public static String decodedBase64Key(String encryptedSecretKey) {
return decodeKey(encryptedSecretKey);
}
public static String aesEncryptedSecretKey() {
return EncryptDecryptImpl.encrypt(mySecretKey, encodedBase64Key());
}
public static String aesDecryptedSecretKey() {
return EncryptDecryptImpl.decrypt(aesEncryptedSecretKey(), encodedBase64Key());
}
}
Test:
String encryptedSecretKey = EncryptDecryptImpl.aesEncryptedSecretKey(); // cipher text
JavaScript:
export const getSecretKey = () => {
const encryptedBase64Key = 'bXVzdEJlMTZCeXRlc0tleQ==';
const parsedBase64Key = enc.Base64.parse(encryptedBase64Key);
const encryptedCipherText = getSessionStorageItem('uselessKey');
let decryptedData = '';
if (encryptedCipherText !== null) {
decryptedData = AES.decrypt(encryptedCipherText, parsedBase64Key, {
mode: mode.ECB, // need to use CBC
padding: pad.Pkcs7
})
}
return decryptedData.toString(enc.Utf8).toString();
}
The roadmap has already been roughly outlined by M. Fehr in his comment. The CBC mode uses an IV. In general it has to be considered that a key/IV pair must not be applied more than once for security reasons. Therefore, the IV is usually randomly generated for each encryption.
The IV must be known during decryption. Hence, it is passed together with the ciphertext. However, since the IV is not secret, it is passed unencrypted, usually concatenated with the ciphertext in the order IV | ciphertext.
For this additional functionality the encrypt() method in the Java code has to be adapted as follows (for simplicity without exception handling):
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
...
private static final String encryptionAlgorithm = "AES/CBC/PKCS5Padding";
private static final String keyAlgorithm = "AES";
...
public static String encrypt(String data, String secret) {
Key key = generateKey(secret);
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
// Generate random IV, encrypt and concatenate IV and ciphertext
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] ciphertext = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] encryptedValue = ByteBuffer.allocate(iv.length + ciphertext.length).put(iv).put(ciphertext).array();
return Base64.getEncoder().encodeToString(encryptedValue);
}
Note that in generateKey() with this change, keyAlgorithm must be used instead of encryptionAlgorithm.
On the JavaScript side, IV and ciphertext must be separated. CBC and PKCS7 are the default and do not need to be specified explicitly.
The ciphertext in the following CryptoJS code was generated with the above C# code and returns the original plaintext:
const enc = CryptoJS.enc, lib = CryptoJS.lib, AES = CryptoJS.AES;
const encryptedBase64Key = 'bXVzdEJlMTZCeXRlc0tleQ==';
const parsedBase64Key = enc.Base64.parse(encryptedBase64Key);
const encryptedCipherText = 'FZ+lnxu9iZGkxmmBxae32ToSkoi+a2/BpzAd6HYnyBjFjCmpssVUVx9N+KQbhpU2ALpJVG8my25KTG6xg7AOXQ==';
const parsedCipherText = enc.Base64.parse(encryptedCipherText);
const iv = lib.WordArray.create(parsedCipherText.words.slice(0, 16 / 4));
const ciphertext = lib.WordArray.create(parsedCipherText.words.slice(16 / 4));
if (encryptedCipherText !== null) {
decryptedData = AES.decrypt({ciphertext: ciphertext}, parsedBase64Key,{iv: iv});
}
console.log(decryptedData.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Decryption in the Java code and encryption in the JavaScript code are to be changed analogously.
Note that old data must be migrated, because data encrypted with ECB mode cannot be decrypted with CBC mode.
Our project is using asymmetric encryption with nacl.box and ephemeral keys:
encrypt(pubKey, msg) {
if (typeof msg !== 'string') {
msg = JSON.stringify(msg)
}
let ephemKeys = nacl.box.keyPair()
let msgArr = nacl.util.decodeUTF8(msg)
let nonce = nacl.randomBytes(nacl.box.nonceLength)
p(`naclRsa.pubKey=${this.pubKey}`)
let encrypted = nacl.box(
msgArr,
nonce,
nacl.util.decodeBase64(pubKey),
ephemKeys.secretKey
)
let nonce64 = nacl.util.encodeBase64(nonce)
let pubKey64 = nacl.util.encodeBase64(ephemKeys.publicKey)
let encrypted64 = nacl.util.encodeBase64(encrypted)
return {nonce: nonce64, ephemPubKey: pubKey64, encrypted: encrypted64}
}
We presently have node.js apps that then decrypt these messages. We would like the option to use jvm languages for some features. There does not seem to be the richness of established players for tweet-nacl on the jvm but it seems
tweetnacl-java https://github.com/InstantWebP2P/tweetnacl-java
and its recommended implementation
° tweetnacl-fast https://github.com/InstantWebP2P/tweetnacl-java/blob/master/src/main/java/com/iwebpp/crypto/TweetNaclFast.java
were a popular one.
It is unclear what the analog to the asymmetric encryption with ephemeral keys were in that library. Is it supported? Note that I would be open to either java or kotlin if this were not supported in tweetnacl-java.
tweetnacl-java is a port of tweetnacl-js. It is therefore to be expected that both provide the same functionality. At least for the posted method this is the case, which can be implemented on the Java side with TweetNaclFast as follows:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import com.iwebpp.crypto.TweetNaclFast;
import com.iwebpp.crypto.TweetNaclFast.Box;
import com.iwebpp.crypto.TweetNaclFast.Box.KeyPair;
...
private static EncryptedData encrypt(byte[] pubKey, String msg) {
KeyPair ephemKeys = Box.keyPair();
byte[] msgArr = msg.getBytes(StandardCharsets.UTF_8);
byte[] nonce = TweetNaclFast.randombytes(Box.nonceLength);
Box box = new Box(pubKey, ephemKeys.getSecretKey());
byte[] encrypted = box.box(msgArr, nonce);
String nonce64 = Base64.getEncoder().encodeToString(nonce);
String ephemPubKey64 = Base64.getEncoder().encodeToString(ephemKeys.getPublicKey());
String encrypted64 = Base64.getEncoder().encodeToString(encrypted);
return new EncryptedData(nonce64, ephemPubKey64, encrypted64);
}
...
class EncryptedData {
public EncryptedData(String nonce, String ephemPubKey, String encrypted) {
this.nonce = nonce;
this.ephemPubKey = ephemPubKey;
this.encrypted = encrypted;
}
public String nonce;
public String ephemPubKey;
public String encrypted;
}
In order to demonstrate that both sides are compatible, in the following a plaintext is encrypted on the Java side and decrypted on the JavaScript side:
First, a key pair is needed on the JavaScript side, whose public key (publicKeyJS) is passed to the Java side. The key pair on the JavaScript side can be generated as follows:
let keysJS = nacl.box.keyPair();
let secretKeyJS = keysJS.secretKey;
let publicKeyJS = keysJS.publicKey;
console.log("Secret key: " + nacl.util.encodeBase64(secretKeyJS));
console.log("Public key: " + nacl.util.encodeBase64(publicKeyJS));
with the following sample output:
Secret key: YTxAFmYGm4yV2OP94E4pcD6LSsN4gcSBBAlU105l7hw=
Public key: BDXNKDHeq0vILm8oawAGAQtdIsgwethzBTBqmsWI+R8=
The encryption on the Java side is then using the encrypt method posted above (and publicKeyJS):
byte[] publicKeyJS = Base64.getDecoder().decode("BDXNKDHeq0vILm8oawAGAQtdIsgwethzBTBqmsWI+R8=");
EncryptedData encryptedFromJava = encrypt(publicKeyJS, "I've got a feeling we're not in Kansas anymore...");
System.out.println("Nonce: " + encryptedFromJava.nonce);
System.out.println("Ephemeral public key: " + encryptedFromJava.ephemPubKey);
System.out.println("Ciphertext: " + encryptedFromJava.encrypted);
with the following sample output:
Nonce: FcdzXfYwSbI0nq2WXsLe9aAh94vXSoWd
Ephemeral public key: Mde+9metwF1jIEij5rlZDHjAStR/pd4BN9p5JbZleSg=
Ciphertext: hHo7caCxTU+hghcFZFv+djAkSlWKnC12xj82V2R/Iz9GdOMoTzjoCDcz9m/KbRN6i5dkYi3+Gf0YTtKlZQWFooo=
The decryption on the JS side gives the original plaintext (using secretKeyJS):
let nonce = "FcdzXfYwSbI0nq2WXsLe9aAh94vXSoWd";
let ephemPubKey = "Mde+9metwF1jIEij5rlZDHjAStR/pd4BN9p5JbZleSg=";
let encrypted = "hHo7caCxTU+hghcFZFv+djAkSlWKnC12xj82V2R/Iz9GdOMoTzjoCDcz9m/KbRN6i5dkYi3+Gf0YTtKlZQWFooo=";
let secretKeyJS = nacl.util.decodeBase64("YTxAFmYGm4yV2OP94E4pcD6LSsN4gcSBBAlU105l7hw=");
let decryptedFromJS = decrypt(secretKeyJS, {nonce: nonce, ephemPubKey: ephemPubKey, encrypted: encrypted});
console.log(nacl.util.encodeUTF8(decryptedFromJS)); // I've got a feeling we're not in Kansas anymore...
function decrypt(secretKey, ciphertext){
let decrypted = nacl.box.open(
nacl.util.decodeBase64(ciphertext.encrypted),
nacl.util.decodeBase64(ciphertext.nonce),
nacl.util.decodeBase64(ciphertext.ephemPubKey),
secretKey
);
return decrypted;
}
<script src="https://cdn.jsdelivr.net/npm/tweetnacl-util#0.15.1/nacl-util.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tweetnacl#1.0.3/nacl.min.js"></script>
My complete code for tweetnacl-java (Kudos to #topaco)
I generated two random key-pairs and saved their secret keys in the application.properties file, so that, i will always have the same pub&sec along with the nonce.
KeyPair baseKeyPair= Box.keyPair();
String baseKeyPairSecretKey = Base64.getEncoder().encodeToString(baseKeyPair.getSecretKey());
KeyPair ephemeralKeyPair= Box.keyPair();
String ephemeralKeyPairSecretKey = Base64.getEncoder().encodeToString(ephemeralKeyPair.getSecretKey());
byte[] nonce = TweetNaclFast.randombytes(Box.nonceLength);
String nonce64 = Base64.getEncoder().encodeToString(nonce);
private final AppConfig config; //you can autowire the config class
private TweetNaclFast.Box.KeyPair getBaseKeyPair() {
byte[] secretKey = Base64.getDecoder().decode(config.getTweetNACLConfig().getBaseSecretKey());
return TweetNaclFast.Box.keyPair_fromSecretKey(mySecretKey);
}
private TweetNaclFast.Box.KeyPair getEphemeralKeyPair() {
byte[] secretKey = Base64.getDecoder().decode(config.getTweetNACLConfig().getEphemeralSecretKey());
return TweetNaclFast.Box.keyPair_fromSecretKey(mySecretKey);
}
private byte[] getNonce() {
return Base64.getDecoder().decode(config.getTweetNACLConfig().getNonce().getBytes(StandardCharsets.UTF_8));
}
public String encrypt(String msg) {
TweetNaclFast.Box.KeyPair baseKeyPair = getBaseKeyPair();
TweetNaclFast.Box.KeyPair ephemeralKeyPair = getEphemeralKeyPair();
byte[] msgArr = msg.getBytes(StandardCharsets.UTF_8);
byte[] nonce = getNonce();
TweetNaclFast.Box box = new TweetNaclFast.Box(baseKeyPair.getPublicKey(), ephemeralKeyPair.getSecretKey());
byte[] encryptedData = box.box(msgArr, nonce);
return Base64.getEncoder().encodeToString(encryptData);
}
public String decrypt(String encryptedData) {
TweetNaclFast.Box.KeyPair baseKeyPair = getBaseKeyPair();
TweetNaclFast.Box.KeyPair ephemeralKeyPair = getEphemeralKeyPair();
byte[] nonce = getNonce();
TweetNaclFast.Box box = new TweetNaclFast.Box(ephemeralKeyPair.getPublicKey(), baseKeyPair.getSecretKey());
byte[] boxToOpen = Base64.getDecoder().decode(encryptedData);
byte[] decryptedData = box.open(boxToOpen, nonce);
return new String(decryptedData, StandardCharsets.UTF_8);
}
> Please, note these two lines
> TweetNaclFast.Box box = new TweetNaclFast.Box(baseKeyPair.getPublicKey(), ephemeralKeyPair.getSecretKey());
> TweetNaclFast.Box box = new TweetNaclFast.Box(ephemeralKeyPair.getPublicKey(), baseKeyPair.getSecretKey());
return encryptAndDecryptData.encrypt("Friday"); // JHo/tk/Jpp2rpxpzIIgBhVhK/CBZLg==
return encryptAndDecryptData.decrypt("JHo/tk/Jpp2rpxpzIIgBhVhK/CBZLg==") //Friday
Before I get too much into the details, the high level thing I'm trying to accomplish is encrypting some data in JavaScript, sending that to a web server, then decrypting that encrypted data in C#. The part I'm having trouble with is decrypting the data in C#.
I'm encrypting some data in JavaScript like this (I removed the extraneous code):
// https://github.com/diafygi/webcrypto-examples#rsa-oaep---encrypt
window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
publicKey,
data
)
.then(function (encrypted) {
// ...
});
I confirmed that I can decrypt it in JavaScript like so (note that I don't actually want to do this, but I did it to prove that the data could be decrypted):
function decryptValue () {
// Base64 decode the encrypted data for the value "Bob".
var data = base64Decode("CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==");
// Get private key.
var keyPromise = importPrivateKey();
return keyPromise.then(function (privateKey) {
// Decrypt the value.
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privateKey,
data
)
.then(function (decrypted) {
// Log the decrypted value to the console.
console.log(arrayBufferToString(decrypted));
});
});
}
For simplicity, that code sample is decrypting a previously encrypted value of "Bob". This works fine.
The problem occurs when I try to decrypt the value in C#:
public static string Decrypt()
{
// The encrypted and base64 encoded value for "Bob".
var encryptedValue = "CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==";
// Assuming RSA-OAEP.
var doOaep = true;
// Setup encryption algorithm.
var provider = GetPrivateKey();
// Decrypt value.
var encryptedData = Convert.FromBase64String(encryptedValue);
// This line throws an error: "Error occurred while decoding OAEP padding."
var decryptedData = provider.Decrypt(encryptedData, doOaep);
var decryptedText = Encoding.Unicode.GetString(decryptedData);
// Return decrypted text.
return decryptedText;
}
The line that says provider.Decrypt(encryptedData, doOaep) throws an error with a message of "Error occurred while decoding OAEP padding." The stack trace is:
Error occurred while decoding OAEP padding.
at System.Security.Cryptography.RSACryptoServiceProvider.DecryptKey(SafeKeyHandle pKeyContext, Byte[] pbEncryptedKey, Int32 cbEncryptedKey, Boolean fOAEP, ObjectHandleOnStack ohRetDecryptedKey)
at System.Security.Cryptography.RSACryptoServiceProvider.Decrypt(Byte[] rgb, Boolean fOAEP)
It seems like maybe the way the JavaScript is encrypting the value is not compatible with the way the C# is encrypting the value. Before I completely abandon this approach and try another JavaScript library for encryption, is there some way around this error?
For additional context, I am guessing this error is related to something mentioned in this article: https://www.codeproject.com/Articles/11479/RSA-Interoperability-between-JavaScript-and-RSACry
It says:
Incompatible padding scheme from the JavaScript code would produce the
"bad data" exception at the server side.
The JavaScript code therefore needs to implement one of two padding
schemes used in the .NET RSA implementation, the first is PKCS#1 v1.5
padding and another is OAEP (PKCS#1 v2) padding.
I'm not getting that exact exception, but maybe since that article was written the error message has changed. In any event, what that article says seems to imply that the way the JavaScript is encrypting isn't compatible with the way the C# is decrypting (namely, due to C#'s requirement for padding).
Is there something I'm missing? Is there some parameter or some easy way to get encryption working in JavaScript and decryption working in C#? Perhaps there is some C# library that decrypts in a way that is compatible with the way the JavaScript is encrypting?
Here's a full example that shows the JavaScript is decrypting properly (only works on some browsers... probably not going to work on IE):
function decryptValue () {
// Base64 decode the encrypted data for the value "Bob".
var data = base64Decode("CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==");
// Get private key.
var keyPromise = importPrivateKey();
return keyPromise.then(function (privateKey) {
// Decrypt the value.
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privateKey,
data
)
.then(function (decrypted) {
// Log the decrypted value to the console.
console.log("Decrypted value: " + arrayBufferToString(decrypted));
});
});
}
function importPrivateKey() {
var rawKey = {
"alg": "RSA-OAEP-256",
"d": "E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ",
"dp": "kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE",
"dq": "qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE",
"e": "AQAB",
"ext": true,
"key_ops": [
"decrypt"
],
"kty": "RSA",
"n": "oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q",
"p": "2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk",
"q": "vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU",
"qi": "Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo"
};
return window.crypto.subtle.importKey(
"jwk",
rawKey,
{
name: "RSA-OAEP",
hash: { name: "SHA-256" }
},
true,
["decrypt"]
);
}
function arrayBufferToString(buffer) {
var result = '';
var bytes = new Uint8Array(buffer);
for (var i = 0; i < bytes.length; i++) {
result += String.fromCharCode(bytes[i]);
}
return result;
}
// Decodes a base64 encoded string into an ArrayBuffer.
// https://stackoverflow.com/a/36378903/2052963
function base64Decode(base64) {
var binary_string = window.atob(base64);
return stringToArrayBuffer(binary_string);
}
// Converts a string to an ArrayBuffer.
function stringToArrayBuffer(value) {
var bytes = new Uint8Array(value.length);
for (var i = 0; i < value.length; i++) {
bytes[i] = value.charCodeAt(i);
}
return bytes.buffer;
}
decryptValue();
BTW, some of my code samples show the private key I'm using. That's intentional to help you understand the code (it's a throw away key). In fact, here's how I am getting the private key in C#:
private static RSACryptoServiceProvider GetPrivateKey()
{
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = Base64UrlDecode("oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q");
RSAparams.Exponent = Base64UrlDecode("AQAB");
RSAparams.D = Base64UrlDecode("E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ");
RSAparams.P = Base64UrlDecode("2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk");
RSAparams.Q = Base64UrlDecode("vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU");
RSAparams.DP = Base64UrlDecode("kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE");
RSAparams.DQ = Base64UrlDecode("qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE");
RSAparams.InverseQ = Base64UrlDecode("Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo");
RSA.ImportParameters(RSAparams);
return RSA;
}
// From the PDF here: https://www.rfc-editor.org/info/rfc7515
// Also see: https://auth0.com/docs/jwks
public static byte[] Base64UrlDecode(string arg)
{
string s = arg;
s = s.Replace('-', '+'); // 62nd char of encoding
s = s.Replace('_', '/'); // 63rd char of encoding
switch (s.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default:
throw new System.Exception(
"Illegal base64url string!");
}
return Convert.FromBase64String(s); // Standard base64 decoder
}
Because you're using OAEP with SHA-2-256 you need to move from RSACryptoServiceProvider to RSACng (.NET 4.6+). Note that aside from the ctor call, I've eliminated the knowledge of which implementation is being used.
private static RSA GetPrivateKey()
{
// build the RSAParams as before, then
RSA rsa = new RSACng();
rsa.ImportParameters(RSAparams);
return rsa;
}
// Setup encryption algorithm.
var provider = GetPrivateKey();
...
var decryptedData = provider.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
I am unable to test #bartonjs's answer because I don't have access to a Windows computer and Mono apparently doesn't implement RSACng. Below is an example that decrypts your ciphertext using the Bouncycastle C# library. Notice the OaepPadding(...) uses SHA-256 for both the Oaep hash and the MGF hash. This is apparently what is needed to interoperate with your javascript code. Also, notice I used Encoding.UTF8.GetString() whereas you used Encoding.Unicode.GetString(). The encoding is definitely not UTF-16 which is what Encoding.Unicode gives you.
using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
namespace RsaSha256OaepDecrypt
{
class MainClass
{
public static void Main(string[] args)
{
var encryptedValue = "CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==";
var encryptedData = Convert.FromBase64String(encryptedValue);
var rsaPrivate = GetPrivateKey();
IAsymmetricBlockCipher cipher0 = new RsaBlindedEngine();
cipher0 = new OaepEncoding(cipher0, new Sha256Digest(), new Sha256Digest(), null);
BufferedAsymmetricBlockCipher cipher = new BufferedAsymmetricBlockCipher(cipher0);
cipher.Init(false, rsaPrivate);
cipher.ProcessBytes(encryptedData, 0, encryptedData.Length);
var decryptedData = cipher.DoFinal();
var decryptedText = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine(decryptedText);
}
private static BigInteger makeBigInt(String b64Url)
{
var bytes = Base64UrlDecode(b64Url);
if ((sbyte)bytes[0] < 0)
{
// prepend a zero byte to make it positive.
var bytes1 = new byte[bytes.Length + 1];
bytes1[0] = 0;
bytes.CopyTo(bytes1, 1);
bytes = bytes1;
}
return new BigInteger(bytes);
}
private static AsymmetricKeyParameter GetPrivateKey()
{
//RSAParameters RSAparams = new RSAParameters();
var Modulus = makeBigInt("oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q");
var Exponent = makeBigInt("AQAB");
var D = makeBigInt("E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ");
var P = makeBigInt("2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk");
var Q = makeBigInt("vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU");
var DP = makeBigInt("kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE");
var DQ = makeBigInt("qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE");
var InverseQ = makeBigInt("Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo");
var rsa = new RsaPrivateCrtKeyParameters(Modulus, Exponent, D, P, Q, DP, DQ, InverseQ);
return rsa;
}
// From the PDF here: https://www.rfc-editor.org/info/rfc7515
// Also see: https://auth0.com/docs/jwks
public static byte[] Base64UrlDecode(string arg)
{
string s = arg;
s = s.Replace('-', '+'); // 62nd char of encoding
s = s.Replace('_', '/'); // 63rd char of encoding
switch (s.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default:
throw new System.Exception(
"Illegal base64url string!");
}
return Convert.FromBase64String(s); // Standard base64 decoder
}
}
}
I am trying to write Javascript to match the output from this Java code:
Java:
import java.util.Base64;
public class Enc2 {
public static void main (String[] arg) {
System.out.println(encryptSomeNumber("1234567812345678"));
}
public static String encryptSomeNumber(final String SomeNumber){
String encryptedSomeNum = "";
String ALGO = "AES";
try {
String myKey = "DLDiGPqGysAow3II";
byte[] keyBytes = myKey.getBytes("UTF-8");
java.security.Key encryptkey = new javax.crypto.spec.SecretKeySpec(keyBytes, ALGO);
javax.crypto.Cipher c;
c = javax.crypto.Cipher.getInstance(ALGO);
c.init(javax.crypto.Cipher.ENCRYPT_MODE, encryptkey);
byte[] encVal = c.doFinal(SomeNumber.getBytes());
byte[] encodedBytes = Base64.getEncoder().encode(encVal);
String s = new String(encodedBytes);
encryptedSomeNum = s;
} catch (Exception e) {
System.out.println("error when encrypting number");
return encryptedSomeNum;
}
return encryptedSomeNum;
}
}
Output: Wrs66TuAIxYe+M4fqyyxtkyMFkWGwx9i45+oQfEA4Xs=
Javascript that I have so far (nodeJS v8.7.0):
let crypto = require('crypto');
let algorithm = 'aes-128-ecb';
let password = 'DLDiGPqGysAow3II';
function encrypt(buffer){
let cipher = crypto.createCipher(algorithm, password)
let crypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
return crypted;
}
let cyphertext = encrypt(new Buffer("1234567812345678", "utf8"))
console.log(cyphertext.toString('base64'));
Output: m1jnKjBbKu+m/zsf9DBTMo3NL4E035l0EailFjt/qjo=
Can anyone see what I'm missing here? Something with PKCS padding?
No, the padding is the same. The problem is that there are two createCipher methods. One is using a password and a key derivation function over the password - this is the one you are using now. The other one uses key and IV. Of course, ECB doesn't use an IV, so you may have to supply an IV value that is then not used.
I am working on mobile application and client side we are using JavaScript (kony) at server side its java. This is working fine for all other device except intel chipset devices (ASUS Zenfone). PFB the JS code for encryption
function encryptDataModeCBC()
{
var encData = "Test";
try
{
var encText = CryptoJS.AES.encrypt(encData, "3f4c57006f7d2d9528de3c46b626df06cdc405cb0243b10ca7612d967c688744", {
iv: "31fd1ae51454cd55db81f1fa60a343ed",
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString(CryptoJS.enc.Base64);
alert ("encText => "+encText);
kony.print("$$$$ encText => "+encText);
}
catch (e)
{
alert(kony.i18n.getLocalizedString("technicalError"));
}
}
Here creating IV & secret key using sha256 & sha512 hashing algorithm.
PFB the code snippet which we are using at server side for decrypting the encrypted string
secret key generation code
private SecretKeySpec getKey(String mode, String msgDigest, String encryptionKey, boolean is256) throws Exception {
byte[] key = encryptionKey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance(msgDigest); // This is SHA-256
key = sha.digest(key);
if (is256) { // This is true in our case.
key = Arrays.copyOf(key, 32);
this.logger.debug("Secret Key " + DigestUtils.sha256Hex(encryptionKey).substring(0, 32));
} else {
key = Arrays.copyOf(key, 16);
this.logger.debug("Secret Key " + DigestUtils.sha256Hex(encryptionKey).substring(0, 16));
}
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
String modeStr = mode.equals("ECB") ? "AES/ECB/PKCS5Padding" : "AES/CBC/PKCS5Padding";
cipher = Cipher.getInstance(modeStr);
return secretKeySpec;
}
IV generation at server side
private IvParameterSpec getIV(String uid, String pin) throws Exception {
String ivValue = new StringBuilder(uid).reverse().toString() + new StringBuilder(pin).reverse();
byte[] key = ivValue.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-256");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
IvParameterSpec iv = new IvParameterSpec(key);
return iv;
}
As I mentioned above this is failing in intel chipset devices. This is the exception which I am getting while decrypting the string
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
When I tried encrypting the string "Test" I am getting "Tn2SzI8dmgCmEvQrzdqLxw==" as encrypted string which I have used in below java code and tried to decrypt where I am getting the below error
enc text => 7b9UNDI4IWNITNAQlYNP8w==
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.ust.Encryptor.decrypt(Encryptor.java:92)
at com.ust.Encryptor.main(Encryptor.java:113)
Here is the JAVA code which I have used for decrypting
package com.ust;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
public class Encryptor {
private static final String AES_PASS = "0ca763dc6b05b5230e44beb6b90e346440204b6d334b09623eafd3fcfbad6a302faca28b0994872e3fd782e7353026684b7ac9385662144e0ed1e2a8e3e14fab79059929681e3794eb97271328ecccda6dbfb3a7991ea1324615cf5908fabdf6"; // Hashed into an AES key later
private SecretKeySpec keyObj;
private Cipher cipher;
private IvParameterSpec ivObj;
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public Encryptor() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException {
// A constant IV, since CBC requires an IV but we don't really need one
String ivValue = new StringBuilder("astring").reverse().toString() + new StringBuilder("0ca763dc6b05b5230e44beb6b90e346440204b6d334b09623eafd3fcfbad6a302faca28b0994872e3fd782e7353026684b7ac9385662144e0ed1e2a8e3e14fab").reverse();
System.out.println("ivValue => "+ivValue);
try {
byte[] ivkey = ivValue.getBytes("UTF-8");
MessageDigest shaIv = MessageDigest.getInstance("SHA-256");
ivkey = shaIv.digest(ivkey);
ivkey = Arrays.copyOf(ivkey, 16);
System.out.println("IV => "+bytesToHex(ivkey));
this.ivObj = new IvParameterSpec(ivkey);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Create an SHA-256 256-bit hash of the key
byte[] key = AES_PASS.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-256");
key = sha.digest(key);
key = Arrays.copyOf(key, 32); // Use only first 256 bit
System.out.println("SEC KEY => "+bytesToHex(key));
this.keyObj = new SecretKeySpec(key, "AES");
// Create a Cipher by specifying the following parameters
// a. Algorithm name - here it is AES
// b. Mode - here it is CBC mode
// c. Padding - e.g. PKCS7 or PKCS5
this.cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public String encrypt(String strDataToEncrypt) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException {
String strCipherText = new String();
this.cipher.init(Cipher.ENCRYPT_MODE, this.keyObj, this.ivObj);
// Encrypt the Data
// a. Declare / Initialize the Data. Here the data is of type String
// b. Convert the Input Text to Bytes
// c. Encrypt the bytes using doFinal method
byte[] byteDataToEncrypt = strDataToEncrypt.getBytes();
byte[] byteCipherText = this.cipher.doFinal(byteDataToEncrypt);
// b64 is done differently on Android
strCipherText = Base64.encodeBase64String(byteCipherText);
return strCipherText;
}
public String decrypt(String strCipherText) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException {
String strDecryptedText = new String();
// Initialize the Cipher for Encryption
this.cipher.init(Cipher.DECRYPT_MODE, this.keyObj, this.ivObj);
// Decode the Base64 text
byte[] cipherBytes = Base64.decodeBase64(strCipherText);
// Decrypt the Data
// a. Initialize a new instance of Cipher for Decryption (normally don't reuse the same object)
// Be sure to obtain the same IV bytes for CBC mode.
// b. Decrypt the cipher bytes using doFinal method
byte[] byteDecryptedText = this.cipher.doFinal(cipherBytes);
strDecryptedText = new String(byteDecryptedText);
return strDecryptedText;
}
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static void main (String args[]) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException{
Encryptor aesCipher = new Encryptor();
try {
String encText = aesCipher.encrypt("Test");
System.out.println("enc text => "+encText);
String plaintext = aesCipher.decrypt("Tn2SzI8dmgCmEvQrzdqLxw==");//("eat6f1uCCXVqJgTNUA8BCqXSA4kG4GhKajXdkyV0TewK+jgDkbQ/lPVaevv4rW3XdSmtVyOKLVJjPw9Akeblrh+ejIv9u48n7PkRKniwfxq/URuPU7lhS/sO5JMiJ7+ufgKFvJapxhSfftCtigtDc8F6Y2lJIPEUeQeQKOVc1noeLqPFggz55hWjWvDtpYh/sG76MwLlWDM7cj+uu6ru3ImmDA7qoM4tJOWBBkfng8u20R1ZcF3gM45TgDLUdL912AE1WO+grGBGjqzTXlK2/jgu3OOsLVI0jndB49K5q3/oKJc7JEoIZb0eZJcuZ80A");
System.out.println("plain text => "+plaintext);
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
CryptoJS assumes that
a key which is passed as a string is actually a password and will hash it again along with a randomly generated salt or it will use the key as-is if it is a WordArray and
the IV should be a WordArray
WordArray is CryptoJS' internal binary data representation.
The code should be:
try {
var key = CryptoJS.enc.Hex.parse("3f4c57006f7d2d9528de3c46b626df06cdc405cb0243b10ca7612d967c688744");
var iv = CryptoJS.enc.Hex.parse("31fd1ae51454cd55db81f1fa60a343ed44");
var encText = CryptoJS.AES.encrypt(encData, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString(CryptoJS.enc.Base64);
alert ("encText => "+encText);
kony.print("$$$$ encText => "+encText);
}
catch (e)
{
alert(kony.i18n.getLocalizedString("technicalError"));
}
Something to think about:
If you send the symmetric key from the server to the client, then anyone who might be listening will get the key and can decrypt the ciphertexts you send. This solution doesn't provide security, but rather obfuscation. You should use TLS which would make the connection actually secure.
The IV must be unpredictable (read: random). Don't use a static IV, because that makes the cipher deterministic and therefore not semantically secure. An attacker who observes ciphertexts can determine when the same message prefix was sent before. The IV is not secret, so you can send it along with the ciphertext. Usually, it is simply prepended to the ciphertext and sliced off before decryption.
It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.