I'm using the following Encrypt / Decrypt in my C# WCF:
public static string EncryptString(string InputText, string Password)
{
RijndaelManaged RijndaelCipher = new RijndaelManaged();
RijndaelCipher.Padding = PaddingMode.ISO10126;
if (string.IsNullOrEmpty(Password) == true)
{
Password = "Test";
}
byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(InputText);
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
//This class uses an extension of the PBKDF1 algorithm defined in the PKCS#5 v2.0
//standard to derive bytes suitable for use as key material from a password.
//The standard is documented in IETF RRC 2898.
PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(Password, Salt);
//Creates a symmetric encryptor object.
ICryptoTransform Encryptor = RijndaelCipher.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
MemoryStream memoryStream = new MemoryStream();
//Defines a stream that links data streams to cryptographic transformations
CryptoStream cryptoStream = new CryptoStream(memoryStream, Encryptor, CryptoStreamMode.Write);
cryptoStream.Write(PlainText, 0, PlainText.Length);
//Writes the final state and clears the buffer
cryptoStream.FlushFinalBlock();
byte[] CipherBytes = memoryStream.ToArray();
memoryStream.Close();
memoryStream = null;
cryptoStream.Close();
cryptoStream = null;
PlainText = null;
Salt = null;
try
{
GC.Collect();
}
catch { }
return Convert.ToBase64String(CipherBytes);
}
public static string DecryptString(string InputText, string Password)
{
RijndaelManaged RijndaelCipher = new RijndaelManaged();
RijndaelCipher.Padding = PaddingMode.ISO10126;
if (string.IsNullOrEmpty(Password) == true)
{
Password = "Test";
}
byte[] EncryptedData = Convert.FromBase64String(InputText);
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
//Making of the key for decryption
PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(Password, Salt);
//Creates a symmetric Rijndael decryptor object.
ICryptoTransform Decryptor = RijndaelCipher.CreateDecryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
MemoryStream memoryStream = new MemoryStream(EncryptedData);
//Defines the cryptographics stream for decryption.THe stream contains decrpted data
CryptoStream cryptoStream = new CryptoStream(memoryStream, Decryptor, CryptoStreamMode.Read);
byte[] PlainText = new byte[EncryptedData.Length];
int DecryptedCount = cryptoStream.Read(PlainText, 0, PlainText.Length);
memoryStream.Close();
memoryStream = null;
cryptoStream.Close();
cryptoStream = null;
Salt = null;
try
{
GC.Collect();
}
catch { }
//Converting to string
return Encoding.Unicode.GetString(PlainText, 0, DecryptedCount);
}
Now, I'm trying to use Java script to fit, want Encrypt data in my web and be able to Decrypt the data in my WCF, I tried to use this script but not work, where I can find Javascript or both JS & .Net sample ?
get the following error:{"Length of the data to decrypt is invalid."}
Thanks.
Ok, if I understand correctly, you want to encrypt a username/password in javascript in a browser in order to safely transport the data to a WCF service. And to accomplish this, you are using AES (symmetric) encryption on both sides.
If that is correct, then you should really be using SSL. Why? Because SSL does this, but much better. In simple terms, SSL will negotiate an AES key after authenticating the public key of an RSA key. So you get the added benefit of the client javascript knowing for sure that it is speaking to the correct server.
What I think is wrong with the roll-your-own AES approach is that at the very least, you have to expose your key (without the public key authentication step) to the client javascript. This means that you are instantly subverting the security, because anyone with that key can now send data to the server.
If I have misunderstood, then perhaps there is an appropriate time to do this, however, at the moment, I don't see one.
Hope this helps.
Related
I have a code for encrypting and decrypting in Java as such
public static String decrypt(String strToDecrypt, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(base64Decode(strToDecrypt)));
}
public static String encrypt(String strToEncrypt, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return base64Encode(cipher.doFinal(strToEncrypt.getBytes("UTF-8")));
}
I tried to encrypt the data in forge js, and decrypt it using Java. All I get is
Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
function aesEncrypt(data, secretKey) {
var cipher = forge.cipher.createCipher('AES-ECB', secretKey)
cipher.start()
cipher.update(forge.util.createBuffer(data))
cipher.finish()
return forge.util.encode64(cipher.output.data)
}
function aesDecrypt(data, secretKey) {
var decipher = forge.cipher.createDecipher('AES-ECB', secretKey)
decipher.start()
decipher.update(forge.util.createBuffer(forge.util.decode64(data)))
decipher.finish()
return decipher.output.data
}
Is there a way to solve this?
Below is the way I generate my key. It is 32 length alphanumeric string.
The secret key is sent in the request itself (Symmetric enc).
SecretKey secretKey = AESEncryptionUtil.generateSecretKey();
public static SecretKey generateSecretKey() throws Exception {
String secretStr = RandomGenerator.getAlphaNumericString(32);
log.info("Generated secret key : {}", secretStr);
byte[] decodeSecretKey = base64Decode(secretStr);
return new SecretKeySpec(decodeSecretKey, 0, decodeSecretKey.length, "AES");
}
The data is then encrypted. Now in JS, it is done as below
function generateSecret(length) {
var result = ''
var characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
var charactersLength = characters.length
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
let aesEncryptedData = aesEncrypt(data, generateSecret(32))
I tried to debug it. I get the plain secret key as it is. The signature is also verified. But the data is not decrypted. I am confused if the Java's SecretKey Interface behaves differently, as in JS I use the plain secret key itself, while in Java I use the SecretKey.
The main problem will be (without seeing more code with a full running example) that you need to use the same generated secretKey on both side (encryption and decryption). As you simply use the output of generateSecret(32) as input for your aesEncrypt-function you do not have access to the same key on decryption side.
let aesEncryptedData = aesEncrypt(data, generateSecret(32))
This is even more important when changing the platforms (javascript to Java). Below you find an example code in Java that takes the data of your encryption part (Javascript) and uses it for decryption.
Security warning: the code is UNSECURE as it uses AES in unsecure mode ECB. The codes does not have any exception handling and is for
educational purpose only.
output:
Is there a difference in generating Secret Key in Java (SecretKey Interface) and forge?
generatedSecret: Mwhq5YXBY6d7zUUbQbVxsJeXvuOz7Sqp
aesEncryptedData: 2TxS64Eku0yO7Na7WwEWinnVwkzrbv2yTIdRA/cW5VURtuTdCyud0Bo4orxP1+ky
decryptedString: The quick brown fox jumps over the lazy dog
code:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class MainSo {
public static void main(String[] args) throws Exception {
System.out.println("Is there a difference in generating Secret Key in Java (SecretKey Interface) and forge?");
String generatedSecret = "Mwhq5YXBY6d7zUUbQbVxsJeXvuOz7Sqp";
String aesEncryptedData = "2TxS64Eku0yO7Na7WwEWinnVwkzrbv2yTIdRA/cW5VURtuTdCyud0Bo4orxP1+ky";
SecretKey secretKey = new SecretKeySpec(generatedSecret.getBytes(StandardCharsets.UTF_8), "AES");
String decrypt = decrypt(aesEncryptedData, secretKey);
System.out.println("generatedSecret: " + generatedSecret);
System.out.println("aesEncryptedData: " + aesEncryptedData);
System.out.println("decryptedString: " + decrypt);
}
public static String decrypt(String strToDecrypt, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
//return new String(cipher.doFinal(base64Decode(strToDecrypt)));
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
}
}
I have the following code for decryption in java, I want that to be implemented in angular4.
public synchronized InputStream getInputStream(String src) {
KeyEntry entry = keysMap.get(src);
try {
String destPath = rootFolder + "/" + entry.destination;
FileInputStream is = new FileInputStream(destPath);
if (entry.key.isEmpty()) return is;
byte[] encKey = Base64.decode(entry.key, Base64.DEFAULT);
SecretKeySpec secretKeySpec = new SecretKeySpec(encKey, AES_ALGORITHM);
IvParameterSpec ivParameterSpec = new IvParameterSpec(encKey);
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
return new CipherInputStream(is, cipher);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
currently doing it, something like below, not working
decryptContent(ciphertext, base64Key) {
const key = CryptoJS.enc.Base64.parse(base64Key);
const decryptedData = CryptoJS.AES.decrypt( ciphertext, key, {
iv: CryptoJS.lib.WordArray.random(128 / 8),
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.NoPadding
});
const decryptedText = decryptedData.toString( CryptoJS.enc.Utf8 );
}
AES_TRANSFORMATION = "AES/CFB8/NoPadding";
Then you need to use CFB mode as well for decryption
mode: CryptoJS.mode.CFB
I don't understand how to get the IV in javascript as in java, that is where I am majorly struck
IvParameterSpec ivParameterSpec = new IvParameterSpec(encKey);
In Java the encryption key is used as IV as well. To decrypt the data you should use the key as IV too.
Please note using key as IV is bad practice and it may create security weakness. IV allows reusing the same key for multiple encryptions without creating an opening for the "two-time pad attack". If the key is reused for multiple encryptions, IV needs to be unique for each encryption for the same key.
I'm having an issue where I was given a file to decrypt along with the Key, IV and encryption method (aes-256-ctr). I have been struggling to decrypt this using node.js and keep running into an error saying the IV length is invalid. I was also given the method to decrypt the file using the BouncyCaste library in C#. The C# method seems to work fine with no issues with the IV. What do I need to do differently in node than what is being done here in C# to avoid this error?
Working C# Decryption (Note: IV is in the filename)
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
namespace c_app
{
class Program
{
static void Main(string[] args)
{
var baseFolder = Directory.GetCurrentDirectory();
// Create parameters
var myDi = new DirectoryInfo(baseFolder);
string keyString = "xxxxxxxxxxxxx32BitKeyxxxxxxxxxxx";
byte[] keyBytes = ASCIIEncoding.UTF8.GetBytes(keyString);
var c = CipherUtilities.GetCipher("AES/CTR/NoPadding");
KeyParameter sk = ParameterUtilities.CreateKeyParameter("AES", keyBytes);
// Get the IV
var files = myDi.GetFiles("001398.20180823.000_1966151284357350418");
var fileInfo = files[0];
var pathSplit = fileInfo.FullName.Split('_');
var filePath = pathSplit[0];
var iv = long.Parse(pathSplit[1]);
// Decrypt the file
var base64String = File.ReadAllText(fileInfo.FullName);
c.Init(false, new ParametersWithIV(sk, BitConverter.GetBytes(iv)));
var inputBytesToDecrypt = Convert.FromBase64String(base64String);
var decryptedBytes = c.DoFinal(inputBytesToDecrypt);
var decryptedText = ASCIIEncoding.UTF8.GetString(decryptedBytes);
// Write file back to current directory
File.WriteAllBytes(string.Format(#"{0}/decrypted.csv", myDi.FullName), decryptedBytes);
}
}
}
My attempt in Node.js
var crypto = require('crypto');
var fs = require('fs');
var iv = Buffer.from('1966151284357350418', 'base64'); // Experimenting with encoding types like base64
var key = Buffer.from('xxxxxxxxxxxxx32BitKeyxxxxxxxxxxx');
fs.readFile('./001398.20180823.000_1966151284357350418', (err, encryptedText) => {
if (err) throw err;
var decipher = crypto.createDecipheriv('aes-256-ctr', key, iv); // <--- Error thrown here
decrypted = decipher.update(encryptedText, 'binary', 'utf8');
decrypted += decipher.final('utf8');
console.log(decrypted);
});
In case it is helpful I was also given the method for encrypting the file using BouncyCastle, which can be found here: https://pastebin.com/PWNzDum3
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.
I'm trying to
generate sign/verification keys (RSA)
sign a value (using those keys) on a Java web application (lets call server-side)
in order to a web client to verify - public-key imported as RSASSA-PKCS1-v1_5 + SHA-256, (in a browser, using WebCrypto API / client-side)
I'm having problems verifying the signed value (signed in the Java server-side) even though the public sign/verify key is successfully imported as a JWK in the client side.
I was wondering if there is any algorithm compatibility issue in any of the steps (OpenSSL, Java or Javascript) that I may be encountering.
The OpenSSL commands used to generate the keys
openssl genrsa -out privatekey.pem 2048
openssl rsa -in privatekey.pem -pubout > publickey.pub
openssl pkcs8 -topk8 -inform PEM -outform DER -in privatekey.pem -out privatekey-pkcs8.pem
Importing keys with Java (server-side)
public static KeyPair generateSignKeyPair() throws ... {
byte[] privBytes = b64ToByteArray(PRIVATE_KEY_PEM_VALUE);
byte[] pubBytes = b64ToByteArray(PUBLIC_KEY_PEM_VALUE);
// private key
KeySpec keySpec = new PKCS8EncodedKeySpec(privBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// public key (javaPubSignKey)
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(pubBytes);
PublicKey publicKey = keyFactory.generatePublic(X509publicKey);
return new KeyPair(publicKey, privateKey);
}
Signing a value with Java (server-side)
public static byte[] generateSignature(PrivateKey signPrivateKey, byte[] data) throws ... {
Signature dsa = Signature.getInstance("SHA256withRSA");
dsa.initSign(signPrivateKey);
dsa.update(data);
return dsa.sign();
}
Send them to a web-app for the WebCrypto API to verify as a client/browser (the client is aware of the publicKey generated in the first step).
// Import public sign/verify key (javaPubSignVerifyKey)
var signatureAlgorithm = {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {
name: 'SHA-256'
}
};
// JWK format (1)
crypto.subtle.importKey(
'jwk', javaPubSignVerifyKey, signatureAlgorithm, false, ['verify']
).then(success, error);
function success(key) {
signatureVerifyPublicKey = key;
}
Note (1): on the Java side, I'm using com.nimbusds.jose.jwk.JWK to export the publicKey to JWK format.
The sign key is successfully imported by WebCrypto. But when it comes to the verification, it fails (the verification boolean is false).
crypto.subtle.verify(
signatureAlgorithm,
signatureVerifyPublicKey,
signature, // bytes in Int8Array format (2)
data // bytes in Int8Array format
).then(
function (valid) {
// valid === false
}
)
Note (2): also note that every example I found on WebCrypto used Uint8Array to represent byte arrays, but since Java generates signed byte-arrays I need to use Int8Array so that the signature values are not contaminated (maybe this is an issue aswell).
EDIT: for reference, it turned out to be another unrelated issue - I was converting the expected data from base64 twice in Javascript without noticing it; naturally the verification failed.
Please, check this simple code based on yours to import a RSA public key (spki) and verify a signature. I have generated the keys and signature using similar Java code
var publicKeyB64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVdZDEs6htb3oxWstz7q+e5IwIRcptMNJiyemuoNyyjtiOy+0tEodjgo7RVoyUcGU3MysEivqvKdswQZ4KfwQCBLAR8DRzp3biAge5utZcKsQoQaC1rCEplfmzEo5ovIlBcMq5x1BxnrnlwEPRmM7MefRa+OeAOQJcstHcrJFO7QIDAQAB";
var dataB64 = "aGVsbG8=";
var signatureB64 = "aEOmUA7YC5gvF6QgH+TMg0erY5pzr83nykZGFtyGOOe+6ld+MC4/Qdb608XiNud+pBpzh0wqd6aajOtJim5XEfCH8vUPsv45aSPtukUIQTX00Oc1frIFDQI6jGJ4Q8dQYIwpqsyE2rkGwTDzt1fTTGiw54pLsJXjtL/D5hUEKL8=";
var signatureAlgorithm = {name: 'RSASSA-PKCS1-v1_5',modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]),hash: { name: 'SHA-256' }};
//convert public key, data and signature to ArrayBuffer.
var publicKey = str2ab(atob(publicKeyB64));
var data = str2ab(atob(dataB64));
var signature = str2ab(atob(signatureB64));
crypto.subtle.importKey("spki", publicKey, signatureAlgorithm, false,["verify"]).
then(function(key){
console.log(key);
return crypto.subtle.verify( signatureAlgorithm, key, signature, data);
}).then( function (valid) {
console.log("Signature valid: "+valid);
}).catch(function(err) {
alert("Verification failed " + err );
});
I could not reproduce exactly the issue. Using the str2ab utility function you have linked, the code works perfectly.
//Utility function
function str2ab(str) {
var arrBuff = new ArrayBuffer(str.length);
var bytes = new Uint8Array(arrBuff);
for (var iii = 0; iii < str.length; iii++) {
bytes[iii] = str.charCodeAt(iii);
}
return bytes;
}
I suggest to compare both codes to find the differences