padding is invalid and cannot be removed decrypt value - c#-4.0

Hello I want to encrypt and Decrypt Text . My encrypt code is working fine and matching the value that i want. But when i want Decrypt this is giving error padding is invalid and cannot be removed . In below code first i am giving my Encrypt and Decrypt both code. Also i have to fix this error Stack overflow link, StackoverlFlow Link 2 but not fix it .
string getHashKey1 = EncryptText("10002:1486703720424", "hpIw4SgN)TxJdoQj=GKo)p83$uHePgoF");
Result = 1ltQFLRGNif73uCNzi0YEvBqLKiRgx6fWsk5e/GcTQc=
string reverseKey = DecryptText('1ltQFLRGNif73uCNzi0YEvBqLKiRgx6fWsk5e/GcTQc=', "hpIw4SgN)TxJdoQj=GKo)p83$uHePgoF");
When i add in AES_Decrypt aes.Padding = PaddingMode.Zeros; i get below result.
Result : -����y�7�t���Ij���,���� Z��$�
public string EncryptText(string input, string password)
{
string result = "";
try
{
// Get the bytes of the string
byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);
result = Convert.ToBase64String(bytesEncrypted);
return result;
}
catch (Exception ex)
{
}
return result;
}
public byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
try
{
using (MemoryStream ms = new MemoryStream())
{
using (Aes aes = Aes.Create())
{
aes.Key = passwordBytes;
aes.Mode = CipherMode.ECB;
// "zero" IV
aes.IV = new byte[16];
using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
}
}
catch (Exception ex)
{
}
return encryptedBytes;
}
Above code is working fine for encrypt .
Below code is giving error
padding is invalid and cannot be removed
public string DecryptText(string input, string password)
{
// Get the bytes of the string
byte[] bytesToBeDecrypted = Convert.FromBase64String(input);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
byte[] bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);
string result = Encoding.UTF8.GetString(bytesDecrypted);
return result;
}
public byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
using (MemoryStream ms = new MemoryStream())
{
using (Aes aes = Aes.Create())
{
aes.Key = passwordBytes;
aes.Mode = CipherMode.ECB;
aes.IV = new byte[16];
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close(); // here i am getting error
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}

You have two problems:
1) (Already pointed out by pedrofb): You use UTF8.GetBytes in encrypt, but SHA256(UTF8.GetBytes()) in decrypt.
You shouldn't do either of these methods, but instead should use a proper Password-Based Key-Derivation Function, such as PBKDF2. In .NET PBKDF2 is available via the Rfc2898DeriveBytes class.
byte[] salt = 8 or more bytes that you always pass in as the same.
// (salt could be fixed for your application,
// but if you have users it should be unique per user and stored along with the output value)
int iterations = 100000;
// Or bigger. If you were making a user management system you
// should write this number down, too, so you can increase it over time;
// it should be whatever number makes it take 100ms or more on the fastest relevant computer)
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
passwordBytes = pbkdf2.GetBytes(16); // 16 = AES128, 24 = AES192, 32 = AES256.
2) You use Base64-encoding in encrypt, but UTF8.GetBytes in decrypt.
Bonus problems:
3) You are using Electronic Codebook (ECB) chaining. Cipher Block Chaining (CBC) is recommended over ECB.
To use CBC properly, let a random initialization vector (IV) be generated in encrypt (which is done automatically when you create a new Aes object, or you can call GenerateIV() in encrypt if you re-use the object). Then you can just prepend the IV (which will always be 16 bytes for AES) to the ciphertext. In decrypt you can either a) chop off the first 16 bytes and assign it as the IV (then decrypt the rest of the data) or b) decrypt the whole blob and ignore the first 16 bytes of decrypted output.

You are hashing the password when you decrypt,
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
but not when encrypt. This means you are using different passwords

Related

Encrypt a string in nodejs and decrypt in java

I am encrypting a text in NODEJS and trying decrypt in Java but getting error.
my nodejs code:
var crypto = require('crypto')
, key = 'mykey#91'
, plaintext = 'SS18617710213463'
, cipher = crypto.createCipher('aes-128-ecb', key)
, decipher = crypto.createDecipher('aes-128-ecb', key);
var encryptedPassword = cipher.update(plaintext, 'utf8', 'base64');
encryptedPassword += cipher.final('base64')
var decryptedPassword = decipher.update(encryptedPassword, 'base64', 'utf8');
decryptedPassword += decipher.final('utf8');
console.log('original :', plaintext);
console.log('encrypted :', encryptedPassword);
console.log('decrypted :', decryptedPassword);
but when I am trying to decrypt it, it always throws an error.
public static String decrypt(String encryptedText) {
try {
final String key = "mykey#91";
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] cipherText = Base64.getDecoder().decode(encryptedText.getBytes("UTF8"));
String decryptedString = new String(cipher.doFinal(cipherText),"UTF8");
return decryptedString;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
The error I am getting as below:
java.security.InvalidKeyException: Invalid AES key length: 8 bytes
The reason you are getting Invalid AES key length: 8 bytes invalid AES is related to length of your key and text. You need to make sure that its length in bits is a power of two. If you want to use a String as your encryption key, check its length in bytes and multiply by 8 to find the length in bits. Also most String implementation will require 2 bytes for every character (Java 64bit). Detailed information here: How to solve InvalidKeyException
In this case, the mentioned error will disappear just using a padded or longer key, for example:
static String PLAIN_TEXT = "SS18617710213463";
static String ENCRYPTION_KEY = "mykey#91mykey#91";
However there is another important thing to consider. The Java Implementation has to match the exact algorithms provided by node.js. This is not as easy as it sounds (at least based on my experience). In your case, I would suggest you to use node-forge on node.js side which is easier to match Java implementations:
var forge = require('node-forge');
var plaintext = 'SS18617710213463';
var key = 'mykey#91mykey#91';
var iv = 'AODVNUASDNVVAOVF';
console.log('Plain Text: ' + plaintext);
var cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(plaintext));
cipher.finish();
var encrypted = cipher.output;
var encodedB64 = forge.util.encode64(encrypted.data);
console.log("Encoded: " + encodedB64);
var decodedB64 = forge.util.decode64(encodedB64);
encrypted.data = decodedB64;
var decipher = forge.cipher.createDecipher('AES-CBC', key);
decipher.start({iv: iv});
decipher.update(encrypted);
var result = decipher.finish();
console.log("Decoded: " + decipher.output.data);
Running the code above, the output should be:
Plain Text: SS18617710213463
Encoded: HCzZD7uc13fqfM6odWcXf/mdR4aNJfkMDhEbnU+asjE=
Decoded: SS18617710213463
And the compatible Java code that will work on the same way looks like the code below:
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Main {
static String PLAIN_TEXT = "SS18617710213463";
static String ENCRYPTION_KEY = "mykey#91mykey#91";
static String INITIALIZATIO_VECTOR = "AODVNUASDNVVAOVF";
public static void main(String [] args) {
try {
System.out.println("Plain text: " + PLAIN_TEXT);
byte[] encryptedMsg = encrypt(PLAIN_TEXT, ENCRYPTION_KEY);
String base64Encrypted = Base64.getEncoder().encodeToString(encryptedMsg);
System.out.println("Encrypted: "+ base64Encrypted);
byte[] base64Decrypted = Base64.getDecoder().decode(base64Encrypted);
String decryptedMsg = decrypt(base64Decrypted, ENCRYPTION_KEY);
System.out.println("Decrypted: " + decryptedMsg);
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] encrypt(String plainText, String encryptionKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/pkcs5padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(INITIALIZATIO_VECTOR.getBytes("UTF-8")));
return cipher.doFinal(plainText.getBytes("UTF-8"));
}
public static String decrypt(byte[] cipherText, String encryptionKey) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/pkcs5padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(INITIALIZATIO_VECTOR.getBytes("UTF-8")));
return new String(cipher.doFinal(cipherText),"UTF-8");
}
}
Which produces:
Plain text: SS18617710213463
Encrypted: HCzZD7uc13fqfM6odWcXf/mdR4aNJfkMDhEbnU+asjE=
Decrypted: SS18617710213463

RSA signing with imported private key

I bought an SSL certificate b/c arvixe does not support self-signed ones. I have a private key in a .pem file which I'd like to use to RSA sign bank transaction parameters. So far, I have not found a way it can be done.
certificate.PrivateKey throws a Keyset not found exception. Using bouncy castle to import the private key (.pem) file works fine, right up to the point where I need to convert to RSACryptoServiceProvider. At that point, DotNetUtilities.ToRSA throws a File Not Found exception. There must be a better way to do this!!!
Here is the relevant snippet from my code:
public string SignRsa(string stringToSign)
{
var encoder = new ASCIIEncoding();
var binData = encoder.GetBytes(stringToSign);
byte[] binSignature;
if (Request.Url.OriginalString.IndexOf("localhost", StringComparison.Ordinal) < 0)
{
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates;
var signed = string.Empty;
X509Certificate2 ipCert = certificates.Find(X509FindType.FindBySubjectName, "www.ingyenpiac.com", false).OfType<X509Certificate2>().First();
RSACryptoServiceProvider rsaCsp;
if (ipCert != null)
{
AsymmetricCipherKeyPair keyPair;
using (var reader = System.IO.File.OpenText(Server.MapPath("~/App_Data/private_key.pem"))) // file containing RSA PKCS1 private key
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPrivate = Convert.ToBase64String(serializedPrivateBytes);
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPublic = Convert.ToBase64String(serializedPublicBytes);
RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(serializedPrivate));
RsaKeyParameters publicKey = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(serializedPublic));
var kpp = keyPair.Private as RsaPrivateCrtKeyParameters;
var ppk = DotNetUtilities.ToRSA(kpp); // <==== File not found exception!!!! WTF???
RSACryptoServiceProvider tempRcsp = (RSACryptoServiceProvider)ppk;
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider(new CspParameters(1, "Microsoft Strong Cryptographic Provider", new Guid().ToString(), new CryptoKeySecurity(), null));
rcsp.ImportCspBlob(tempRcsp.ExportCspBlob(true));
ipCert.PrivateKey = rcsp;
if (ipCert.Verify())
{
rsaCsp = (RSACryptoServiceProvider)ipCert.PrivateKey;
}
else
throw new ApplicationException("Certificate failed to verify.");
}
else
throw new ApplicationException("SignRsa: No certifciate found");
using (var sha = new SHA1CryptoServiceProvider())
{
binSignature = rsaCsp.SignData(binData, sha);
}
if (rsaCsp.VerifyData(binData, new SHA1CryptoServiceProvider(), binSignature))
signed = BitConverter.ToString(binSignature).Replace("-", string.Empty);
store.Close();
return signed;
}
return null;
}
I sure hope someone can help me with this!
With .NET 4.6 this code can get simplified a bit:
public string SignRsa(string stringToSign)
{
var signed = string.Empty;
using (var ipCert = new X509Certificate2(Server.MapPath("~/App_Data/pfxFile.pfx"), "password"))
using (var RSA = ipCert.GetRSAPrivateKey())
{
// Note, if the cert was not RSA, or had no private key, RSA
// will be null. But you didn't check it, so I won't.
var binData = System.Text.Encoding.ASCII.GetBytes(stringToSign);
byte[] binSignature = RSA.SignData(binData, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
// Not sure why you want to re-verify the signature, but OK:
if (RSA.VerifyData(binData, binSignature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1))
signed = BitConverter.ToString(binSignature).Replace("-", string.Empty);
return signed;
}
}
Wow! I really overcomplicated things. No need for bouncy castle, or to do contortions. The answer was quite simple, shown below:
public string SignRsa(string stringToSign)
{
ASCIIEncoding encoder;
var signed = string.Empty;
var ipCert = new X509Certificate2(Server.MapPath("~/App_Data/pfxFile.pfx"), "password");
var RSA = (RSACryptoServiceProvider)ipCert.PrivateKey;
encoder = new ASCIIEncoding();
var binData = encoder.GetBytes(stringToSign);
byte[] binSignature;
using (var sha1 = new SHA1CryptoServiceProvider())
binSignature = RSA.SignData(binData, sha1);
if (RSA.VerifyData(binData, new SHA1CryptoServiceProvider(), binSignature))
signed = BitConverter.ToString(binSignature).Replace("-", string.Empty);
return signed;
}

Secure token created in node different from .net

I'm trying to implement reCaptcha Secure Tokens in nodejs.
Looked at the examples made in Java and in .NET and created this version for node:
exports.getSecureToken = function() {
var algorithm = 'aes-128-ecb';
var tokenObj = { session_id: 'ab0069ec-3c2c-436c-868b-43c7a10db229'/*uuid.v4()*/, ts_ms: 1446560931992/*(new Date()).getTime()*/ };
var text = JSON.stringify(tokenObj);
var shaHash = new Buffer(crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO'/*config.reCAPTCHASecret*/).digest('hex'), 'hex');
var key = shaHash.slice(0, 16);
var cipher = crypto.createCipher(algorithm, key, key);
var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
return result;
};
Problem is that in .NET I get a token that works (using the right key not included in the post), but in node I get a different token for the same input, and it doesn't works:
.NET - LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
node - EGr7drd1JEylwzLGakZ6dpPRSf2nFdpzHOrJlLZlyHYmVRj5obAw7WjPt4W5l0vsywNEqCQ-2_d7qIZOMiOedianfBrQPOBaOmmq44IOB8Q
I got to see that key and input are the same (in .NET and node) right at the moment before encryption, so the problem must(?) be the cipher, any clues?
.NET code for reference:
public static void Main(string[] args)
{
//Your code goes here
Console.WriteLine(EncryptJsonToken(GetJsonToken()));
}
public static string GetJsonToken()
{
//Example: {"session_id": e6e9c56e-a7da-43b8-89fa-8e668cc0b86f,"ts_ms":1421774317718}
string jsonRequest = "{" + string.Format("\"session_id\": {0},\"ts_ms\":{1}", "ab0069ec-3c2c-436c-868b-43c7a10db229", 1446560931992) + "}";
return jsonRequest;
}
public static byte[] getKey()
{
string secretKey = "6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO";
SHA1 sha = SHA1.Create();
byte[] dataToHash = Encoding.UTF8.GetBytes(secretKey);
byte[] shaHash = sha.ComputeHash(dataToHash);
byte[] first16OfHash = new byte[16];
Array.Copy(shaHash, first16OfHash, 16);
return first16OfHash;
}
public static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
public static string EncryptJsonToken(string jsonToken)
{
byte[] encrypted = EncryptStringToBytes_Aes(jsonToken, getKey(), getKey());
//Base64 encode the encrypted data
//Also applys the URL variant of base64 encoding, unfortunately the HttpServerUtility.UrlTokenEncode(encrypted) seems to truncate the last value from the string so we can't use it?
return Convert.ToBase64String(encrypted, Base64FormattingOptions.None).Replace("=", String.Empty).Replace('+', '-').Replace('/', '_');
}
To debug in .NET: DEMO
You have two problems:
You're using JSON.stringify() to produce a valid JSON string, but the GetJsonToken() method in the C# code doesn't produce a valid JSON string. There are " missing for the UUID and there is a space between the session_id key and its value for some reason. You have to reflect those differences in JavaScript:
var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
var time = 1446560931992;
var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
There is no such function crypto.createCipher(algorithm, key, key). There is however crypto.createCipheriv(algorithm, key, iv). createCipher(algorithm, password) can be used if one has a password instead of a key which you don't have. Since there is no IV for ECB mode, you can pass in an empty (binary) string as the IV.
Full code:
var crypto = require("crypto");
var algorithm = 'aes-128-ecb';
var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
var time = 1446560931992;
var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
console.log("Token: " + text);
var shaHash = crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO').digest();
var key = shaHash.slice(0, 16);
var cipher = crypto.createCipheriv(algorithm, key, "");
var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
console.log("result: " + result);
console.log("expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI");
Output:
Token: {"session_id": ab0069ec-3c2c-436c-868b-43c7a10db229,"ts_ms":1446560931992}
result: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI

Convert DESede/ECB/NoPadding algorithm written in java into Nodejs using crypto module

I am working on migration of code form java to Nodejs. I have one requirement to encrypt the text with private key using "DESede/ECB/NoPadding" algorithm. Currently code is written in Java and now I need to migrate to Nodejs. Since encrypted key is sent to other application therefore I can't change the algorithm or key here. Following is approach used in java
1. Stored the private key in hex string. I.e. 48 chars hex string as below which is equivalent to 24 bytes reuquired for 3des
73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2
2. Following is code written in java
https://github.com/dilipkumar2k6/3des/blob/master/TripleDes.java
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class TripleDes {
// Crypto library related keys
private static final String ALGO_NAME = "DESede/ECB/NoPadding";
private static final int PADDING_BLOCK = 8;
// Test Data
private static final String PLAIN_TEXT = "Hello World";
private static final String SHARED_KEY = "73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2";
public static void main(String[] arg) {
try {
// Get Algorithm name
String desAlgoName = getDESAlgorithmName(ALGO_NAME);
// Create Cipher object
Cipher cipher = Cipher.getInstance(ALGO_NAME);
//Actual DES algo needs 56 bits key, which is equivalent to 1byte (0 at 0th position) Get 8*3 byets key
byte [] key = hexFromString(SHARED_KEY);
System.out.println("DES Algorithm shared key size in bytes >> "+key.length);
// Create SecretKeySpec
SecretKeySpec secretKeySpec = new SecretKeySpec(key, desAlgoName);
//Encrypt bytes
byte [] encryptedBytes = encryptIntoBytes(cipher, secretKeySpec, PLAIN_TEXT.getBytes(), 0, PLAIN_TEXT.getBytes().length);
String encryptedString= hexToString(encryptedBytes);
System.out.println(encryptedString);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static byte[] encryptIntoBytes(Cipher cipher, SecretKeySpec secretKeySpec, byte[] dct, int offset, int len) throws GeneralSecurityException {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] ect = cipher.doFinal(addPadding(dct, offset, len));
return ect;
}
public static String getDESAlgorithmName(String algoName) {
System.out.println("getDESAlgorithmName algoName >> "+algoName);
String desAlgoName = null;
int i = algoName.indexOf("/");
if (i != -1)
desAlgoName = algoName.substring(0, i);
else
desAlgoName = algoName;
return desAlgoName;
}
/**
* Adds padding characters to the data to be encrypted. Also adds random
* Initial Value to the beginning of the encrypted data when using Triple
* DES in CBC mode (DES-EDE3/CBC).
*
* #param inData
* Array of bytes to be padded
* #param offset
* Offset to starting point within array
* #param len
* Number of bytes to be encrypted
* #return Padded array of bytes
*/
public static byte[] addPadding(byte[] inData, int offset, int len) {
System.out.println("addPadding offset >> "+offset+", len >> "+len);
byte[] bp = null;
int padChars = PADDING_BLOCK; // start with max padding value
int partial = (len + 1) % padChars; // calculate how many extra bytes
// exist
if (partial == 0) {
padChars = 1; // if none, set to only pad with length byte
} else {
padChars = padChars - partial + 1; // calculate padding size to
// include length
}
System.out.println("addPadding >> Add padding of "+padChars);
/*
* Create a byte array large enough to hold data plus padding bytes The
* count of padding bytes is placed in the first byte of the data to be
* encrypted. That byte is included in the count.
*/
bp = new byte[len + padChars];
bp[0] = Byte.parseByte(Integer.toString(padChars));
System.arraycopy(inData, offset, bp, 1, len);
return bp;
}
public static byte[] hexFromString(String hex) {
int len = hex.length();
byte[] buf = new byte[((len + 1) / 2)];
int i = 0, j = 0;
if ((len % 2) == 1)
buf[j++] = (byte) fromDigit(hex.charAt(i++));
while (i < len) {
buf[j++] = (byte) ((fromDigit(hex.charAt(i++)) << 4) | fromDigit(hex
.charAt(i++)));
}
return buf;
}
public static int fromDigit(char ch) {
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
throw new IllegalArgumentException("invalid hex digit '" + ch + "'");
}
public static String hexToString(byte[] ba) {
return hexToString(ba, 0, ba.length);
}
public static final char[] hexDigits = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static String hexToString(byte[] ba, int offset, int length) {
char[] buf = new char[length * 2];
int j = 0;
int k;
for (int i = offset; i < offset + length; i++) {
k = ba[i];
buf[j++] = hexDigits[(k >>> 4) & 0x0F];
buf[j++] = hexDigits[k & 0x0F];
}
return new String(buf);
}
}
I need to migrate this code to Nodejs and facing multiple issues. I refeered http://mygo.iteye.com/blog/2018882 to get the basic idea on nodejs way to do the encryption in des3. However I see following difference in JAVA way and Nodejs way.
1. JAVA is using Hex string of 48 lenght as key, since one char in hex is 4 bits therfore final size is equivalent to 24 bytes length which meets DES3 requirement.
2. In Java code, final key is being used as bytes (as needed by DES) which made indpendent of the way we store the key
3. In node js, key is stored as character i.e. to use des3 I have to use 24 bytes which is equivalent to 24 chars key as 73AD9CEC99816AA6A4D82FB2. Here this is string of 24 chars and since one char is one byte thereofore total length is 24 bytes which meets DES3 requirement.
4. Following is nodejs code for reference
https://github.com/dilipkumar2k6/3des/blob/master/Crypto.js
'use strict';
/*
* Offers related services.
*/
var crypto = require("crypto");
module.exports = {
encrypt: function (plainText) {
return encrypt({
alg: 'des-ede3', //3des-ecb
autoPad: true,
key: '73AD9CEC99816AA6A4D82FB2',
plaintext: 'Hello World',
iv: null
});
}
};
function encrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0);
var plaintext = param.plaintext;
var alg = param.alg;
var autoPad = param.autoPad;
//encrypt
var cipher = crypto.createCipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad); //default true
var ciph = cipher.update(plaintext, 'utf8', 'hex');
ciph += cipher.final('hex');
console.log(alg, ciph);
return ciph;
}
function decrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0)
var alg = param.alg;
var autoPad = param.autoPad;
//decrypt
var decipher = crypto.createDecipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad);
var txt = decipher.update(ciph, 'hex', 'utf8');
txt += decipher.final('utf8');
console.log(alg, txt);
return txt;
}
Following is my problem.
1. How can i convert my existing hex code into string? I used "hexToString" method (please check the java code)to convert hex into string. However getting weired character (this is also expected but problem is how i can use this transformed key in nodejs.
2. Can I pass byte array as key to Nodejs? It will make problem easy as I can easily convert my hex key into bytes array and I store my bytes array key in nodejs code.
3. In my javacode, I have custom padding logic, how can i write same logic in nodejs?
4. Most importantly, can I achieve same encryption logic in nodejs (similar to java)?
Artjom B. helped me to get the insight of nodejs and des3 algorithm. I have edited my post to clarify my exact requirement.
I think my main problem is, how can i feed byte[] as key to nodejs crypto for DES3?
I am kind of stuck. Please help.
Running crypto.getCiphers() shows you the available ciphers. Triple DES (EDE) in ECB mode with two keys (16 byte key) can be used as des-ede. If you have three part key (24 byte key) you should use des-ede3. ecb probably does not appear in the cipher description, because it's the most basic form.
Triple DES-EDE has different ways to use a key. EDE means encrypt-decrypt-encrypt with three different keys. If you only have for example one 8 byte key, this suggests that you use the same key for every phase of EDE. It's clear from your Java code that you have a 24 byte key (48 hex encoded chars). You have to use the same key.
The crypto module uses PKCS7 padding by default, so you will need to set the auto padding to false and do the padding yourself. I leave that task up to you.
module.exports = {
encrypt: function (plainText) {
return encrypt({
alg: 'des-ede3', //3des-ecb
autoPad: false,
key: '73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2',
plaintext: 'Hello World',
iv: null
});
}
};
function mypad(buf){
// TODO: do the padding
// replicate padding as in Java
return buf;
}
function myunpad(buf){
// TODO: do the unpadding
// read the first *byte* and remove as many trailing *bytes*
return buf;
}
function encrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0);
var plaintext = mypad(new Buffer(param.plaintext));
var alg = param.alg;
var autoPad = param.autoPad;
//encrypt
var cipher = crypto.createCipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad); //default true
var ciph = cipher.update(plaintext, 'utf8', 'hex');
ciph += cipher.final('hex');
console.log(alg, ciph);
return ciph;
}
function decrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0)
var alg = param.alg;
var autoPad = param.autoPad;
//decrypt
var decipher = crypto.createDecipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad);
var txt = decipher.update(ciph, 'hex', 'utf8');
txt += decipher.final('utf8');
console.log(alg, txt);
return myunpad(new Buffer(txt, 'hex'));
}
Word of caution:
Don't use (3)DES especially with only one 8 byte key! Don't use ECB mode! Don't use NoPadding for block modes! Use AES-256 with GCM mode (no padding since it's a streaming mode).-
This is my decrypt function based on Artjom's answer. use 'des-ede3' if you have a 24byte key.
internals.decrypt = function (message, key) {
var message = Buffer.from(message, 'base64');
var decipher = crypto.createDecipher('des-ede', key);
var decryptedMessage = decipher.update(message, 'hex', 'utf8');
decryptedMessage += decipher.final('utf8');
return decryptedMessage.toString();
}

AES-128-ECB - Inconsistent encryption result of node js and java

node js code:
function AES_encrypt(){
var bKey = new Buffer('24Qn9974h50D9DNi', 'utf-8');
var bInput = new Buffer(‘test’, 'utf-8');
console.log(bKey.length);
var cipher = crypto.createCipher('AES-128-ECB',bKey);
//cipher.setAutoPadding(auto_padding=false);
var crypted = cipher.update(bInput,null,'base64');
crypted+=cipher.final('base64');
console.log(crypted);
return crypted;
}
get Result:57b6b7oulw7eO5h7efZ9/w==
java code:
main java:
String data = AES.encryptToBase64("test","24Qn9974h50D9DNi");
AES java:
public static String encryptToBase64(String data, String key){
try {
byte[] valueByte = encrypt(data.getBytes("utf-8"), key.getBytes("utf-8");
return new String(Base64.encode(valueByte));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("encrypt fail!", e);
}
}
public static byte[] encrypt(byte[] data, byte[] key) {
if(key.length!=16){
throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
}
try {
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec seckey = new SecretKeySpec(enCodeFormat,"AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, seckey);// 初始化
byte[] result = cipher.doFinal(data);
return result; // 加密
} catch (Exception e){
throw new RuntimeException("encrypt fail!", e);
}
}
get Result:wA1JU6VxMaVl8Ck8pBrX8A==
Use crypto.createCipheriv to solve the issue,
http://nodejs.org/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv
You need to pad the string "test" to 16 bytes. I believe Java uses PKCS padding by default (but there are other padding schemes, too).
String data = AES.encryptToBase64("test","24Qn9974h50D9DNi");

Resources