I have the following requirements regarding encryption of url parameters. As I am newly to CryptoJS I can not figure out how to perform all steps using CryptoJS. Can anybody help me with example code to perform this steps?
Encrypt Requirements
Generate a secret key from the given password.
Receive the password which is coupled with the system name. (This is already available)
Generate a random nonce in byte-array as the password’s salt which has a length 16-bytes.
Set the number of iterations for secret key as 65536.
Set the length of secret key as 256-bit (not bytes).
Set the algorithm name for key derivation using PBKDF2 with HMAC and SHA-2.
Set the algorithm name of key generation as “AES”.
Create a secret key with the function of library or tool with all sets above.
Encrypt the JSON-string with the above generated secret key.
Generate a random nonce in byte-array as the IV (initial vector) which has a length 12-bytes for encryption.
Select encrypt mode for the crypto in your function or tool for encryption.
Set the name of crypto algorithm which is for AES in GCM mode and without any padding.
Set the length of authentication tag for GCM as 128-bits (not bytes).
Convert your JSON-string which contains customer information to a byte-array(plainText) with character set UFT-8.
Encrypt the plainText with all sets and the secret key in last step to a new byte-array(cipherText)
Prefix the generated IV and password’s salt to the cipherText .
Create a new byte-array (cipherTextWithIvSalt).
Add the 12-bytes IV in cipherTextWithIvSalt at first.
Add the 16-bytes password’s salt in cipherTextWithIvSalt after that.
Add the cipherText in cipherTextWithIvSalt at last.
cipherTextWithIvSalt is now in form “IV + salt + cipherText”
Encode the cipherTextWithIvSalt.
Encode the cipherTextWithIvSalt to a new byte-array (encodedCipherTextWithIvSalt) by using Base64
Build a displayable encrypted string.
Build a new string from byte-array encodedCipherTextWithIvSalt with character set UTF-8.
I have tried to do it myself but it does not work here is the example of my js code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
let secretKey = generateKey(salt);
let iv = CryptoJS.lib.WordArray.random(96 / 8);
let JSON_String = "{"Name":"Max", "Firstname":"Mustermann"}";
let plainText = CryptoJS.enc.Utf8.parse(JSON_String);
let cipherText = encryptJSONString(plainText, secretKey, iv);
let cipherTextWithIvSalt = prefixCipherTextWithSaltAndIV(iv, secretKey, cipherText);
let encodedCipherTextWithIvSalt = CryptoJS.enc.Base64.parse(cipherTextWithIvSalt);
let displayableText = CryptoJS.enc.Utf8.stringify(encodedCipherTextWithIvSalt);
function generateKey(password) {
var salt = CryptoJS.lib.WordArray.random(128 / 8);
return CryptoJS.PBKDF2(password, salt, { keySize: 256 / 32, iterations: 65536, hasher: CryptoJS.algo.SHA256 });
}
function encryptJSONString(plainText, secretKey, iv) {
//mode: CryptoJS.mode.GCM
return CryptoJS.AES.encrypt(plainText, secretKey, { iv: iv, padding: CryptoJS.pad.NoPadding });
}
function prefixCipherTextWithSaltAndIV(iv, secretKey, cipherText) {
let byteArray = [];
byteArray.push(iv.words);
byteArray.push(secretKey.words);
byteArray.push(cipherText);
return byteArray;
}
Thank you very much in advance!
Related
I am trying to generate encrypted text in Node using Crypto.js using its AES algorithm. It works in Js for encryption and decryption both. Similarly I tried implementing same in Python using pycrypto and it does encryption and decryption both in python. But the problem arises when I want to use encrypted cipher text from JS to decrypt in Python. The problem is that the encrypted text generated in JS is different from what is generated in Python.
Here is the JS based usage of AES:
import CryptoJS from "crypto-js";
let str = "lol";
let salt = "ABCDEFGHIJKLMNOP";
let key = "ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP";
salt = CryptoJS.enc.Hex.parse(salt);
key = CryptoJS.enc.Hex.parse(key);
const options = {
iv: salt,
padding: CryptoJS.pad.ZeroPadding,
mode: CryptoJS.mode.CFB,
};
// function to encrypt the string
function encrypt(str, salt, key) {
const cipher = CryptoJS.AES.encrypt(str, key, options);
console.log("cipher", CryptoJS.enc.Hex.stringify(cipher.ciphertext));
return cipher.ciphertext.toString(CryptoJS.enc.Base64);
}
// function to decrypt the string
function decrypt(str, salt, key) {
const cipher = CryptoJS.AES.decrypt(str, key, options);
return cipher.toString(CryptoJS.enc.Utf8);
}
const encrypted = encrypt(str, salt, key);
console.log("encrypted", encrypted);
const decrypted = decrypt(encrypted, salt, key);
console.log("decrypted", decrypted);
// OUTPUT
// encrypted sd4Xpz/ws8x2j+cgF17t6A==
// decrypted lol
Here is the Python based usage of AES:
from Crypto.Cipher import AES
from base64 import b64encode, b64decode
str = "lol";
salt = b"ABCDEFGHIJKLMNOP";
key = "ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP";
enc_dec_method = 'utf-8'
def encrypt(str_to_enc, str_key, salt):
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt)
hx_enc = aes_obj.encrypt(str_to_enc.encode('utf8'))
df = b64encode(hx_enc)
mret = b64encode(hx_enc).decode(enc_dec_method)
return mret
def decrypt(str_to_dec, str_key, salt):
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt)
str_tmp = b64decode(str_to_dec.encode(enc_dec_method))
str_dec = aes_obj.decrypt(str_tmp)
mret = str_dec.decode(enc_dec_method)
return mret
test_enc_text = encrypt(str, key, salt)
test_dec_text = decrypt(test_enc_text, key, salt)
print(f"Encrypted Text: {test_enc_text}")
print(f"Decrypted Text: {test_dec_text}")
# OUTPUT
# Encrypted Text: o9XB
# Decrypted Text: lol
I tired encrypting and decrypting in both the languages and check the the documentation and source code on both the libraries.
Did also trying checking if I am missing encoding or decoding, but had no luck.
What key concept am I missing here which can help bridge the gap of compatibility here?
The codes are incompatible because:
Key and IV are not hex encoded, so the hex encoder must not be used in the CryptoJS code. If Utf-8 encoding is to be applied as in the Python code, the Utf-8 encoder must be used instead.
salt = CryptoJS.enc.Utf8.parse(salt);
key = CryptoJS.enc.Utf8.parse(key);
CFB is a stream cipher mode that does not require padding. However, in the current CryptoJS code, Zero padding is used (CryptoJS.pad.ZeroPadding). Instead, the padding has to be disabled (which does not happen implicitly, unlike in the Python code):
padding: CryptoJS.pad.NoPadding,
CFB is configured with an additional parameter, the segment size, which specifies the number of bits encrypted per encryption step. The CryptoJS code supports only one segment size, 128 bits. With PyCryptodome the segment size can be configured, by default 8 bits are used. For compatibility it must be changed to 128 bits:
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt, segment_size=128)
With these changes both codes are compatible.
Test: Both codes provide the following ciphertext for the following plaintext:
plaintext: The quick brown fox jumps over the lazy dog
ciphertext: m0T/7e04eV49RTcgd7KtwHxSOavNzHNwlrvjt1YmmHidBy4rHS0oovclKQ==
Note that what you call salt is actually the initialization vector (IV). The term salt is more commonly used in the context of key derivation functions (like PBKDF2).
Regarding security: The values used for salt and IV are OK for testing, but in a real world scenario a random byte sequence has to be applied for the IV for each encryption, which has to be passed along with the ciphertext to the decrypting side (generally concatenated).
Also, as key no passphrase has to be used, but a random byte sequence. A passphrase (a strong one of course) may only be applied in combination with a key derivation function.
I am writing Python Decryption logic to decrypt the message (client-side it is encrypted using Java).
Python code:
def decrypt(self, text):
decode = base64.b64decode(text)
cryptor = AES.new(InKey, MODE_CBC, InIV)
plain_text = cryptor.decrypt(decode)
return unpad(plain_text)
I have received below key & IV values (sample values) from the client team:
InKey =
"6asd6587daIs8g2qvi3rJbM9sdasd6cb2kdYC0TOy5zEgTo+8LrQn0UJZAmJCtmX......"(it
is length of 684 chars) InIV =
"7as76cascsagoKtID7z1nUakJqzj+Dwl9cL9Q2/zBFbs0Sg3Kw6US8yvvzbkyg2bnjGHWofIWrhMQ/Bcde...."
(it is length of 684 chars)
Question: While running the above python code, getting the below error. How to convert the above IV value into 16bytes in size and also key-value length?
ValueError: Incorrect IV length (it must be 16 bytes long)
existing Decryption Java Client follows Hybrid pattern(first using RSA private key to pull two params(IV & Key)...and then AES cipher to decrypt actual message):
Pseudo code:
................................
//Step-1) Generate RSA-PrivateKey
................................
RSAPrivateKey serverPrivateKey = KeyUtils.getPrivateKey("lib", PrivKey);
/* here PrivKey refers to .der certificate
and FYI- KeyUtils actually refers to a class, that generate RSA PrivateKey with below code:
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
KeySpec ks = new PKCS8EncodedKeySpec(privKeyBytes);
return (RSAPrivateKey)keyFactory.generatePrivate(ks);
*/
................................
//Step-2) use RSA-PrivateKey to decrypt the message
................................
MessageEncryption encryptor = new MessageEncryption(serverPrivateKey);
/* initializes Cipher(RSA instance) with passed RSA PrivateKey
this.m_asymmetricalKey = serverPrivateKey;
this.m_asymmetricalCipher = Cipher.getInstance(this.m_asymmetricalKey.getAlgorithm());
*/
encryptor.decryptMessage(JSONresponseBody); // invokes below mentioned method
...............
decryptMessage(JSONObject jsonMessage)
{
.............................
IvParameterSpec initializationVector = new IvParameterSpec(this.m_asymmetricalCipher.doFinal(StringCodec.decodeBase64(jsonMessage.getString("iv").getBytes())));
this.m_asymmetricalCipher.init(2, this.m_asymmetricalKey);
Key secretKey = new SecretKeySpec(this.m_asymmetricalCipher.doFinal(StringCodec.decodeBase64(jsonMessage.getString("key").getBytes())), "AES");
Cipher symmetricalCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
symmetricalCipher.init(2, secretKey, initializationVector);
return new String(symmetricalCipher.doFinal(StringCodec.decodeBase64(jsonMessage.getString("message").getBytes())));
.............................
}
..............
So at Python how to get right key-size & IV, is there way to implement similar hybrid pattern at java or does it any other approach to deal with at python ?
Thanks.
Your Java-side code does the following things:
Read the PKCS8-encoded RSA key
Decode the base64-encoded IV
Decrypt the IV using the private key (a side-note: encrypting IV makes very little sense to me)
Decode the base64-encoded AES key
Decrypt the AES key using the private key
Decode the base64-encoded cyphertext
Decrypt the ciphertext using the obtained IV and AES key
You need to re-implement each of the above steps in Python. Some are trivial, like base64-decoding. The others depend on the cryptography libraries you're using.
I found encrypt package for flutter to encrypt and decrypt a message, I succeded to encrypt and decrypt but don't succeded to use this in a cas of separated device like illustrated.
Here is an example, where I compose a plaintext with a static string and a random number, it permit to change the generated key and when I decrypt, found the static string with regex match
var number = new Random();
var random= number.nextInt(100);
final plainText = 'static_name$random';
final key = Key.fromLength(16);
final iv = IV.fromLength(8);
final encrypter = Encrypter(AES(key));
final encrypted = encrypter.encrypt(plainText, iv: iv);
final decrypted = encrypter.decrypt(encrypted, iv: iv);
print(encrypted.base64);//my plaintext is encrypted fesesgesgneslg465esg6es4g
print(decrypted); //my random plaintext is decrypted static_name$rnd
//my regex match function
Currently I don't find how to enter my encrypted key ( fesesgesgneslg465esg6es4g) I serach to do something like that
//---------on press I generate a random key that I encrypt----
var rng = new Random();
var rnd= rng.nextInt(100); //choisir le nombre max de contenu de la catégorie
final plainText = 'static_name$rnd';
final key = Key.fromLength(16);
final iv = IV.fromLength(8);
final encrypter = Encrypter(AES(key));
final encrypted = encrypter.encrypt(plainText, iv: iv);
//output : 68e4sg68es4ges68g4
//---------the user enter the key(68e4sg68es4ges68g4) on a second device ----
encrypted=68e4sg68es4ges68g4;
final key = Key.fromLength(16);
final iv = IV.fromLength(8);
final encrypter = Encrypter(AES(key));
final decrypted = encrypter.decrypt(encrypted, iv: iv);
print(decrypted);
I can't found how to decrypt my key ( 68e4sg68es4ges68g4)
In few word I succeded to encrypt and decrypt automatically an input but don't succeded to manually add the generated key to the decrypt function.
There's a serious problem with how you are trying to use the encrypt package. By using Key.forLength() you are basically using a key of 0000000....000. The same for IV. That's not a very secure key!
When using a crypto system between two different machines you need to find some way to share the key between them - the so-called "shared secret". You might generate this from a "passphrase" using, for example, PKKDF2. Or you could just compile a random byte string into the code, but expect that an attacker could reverse-engineer your code.
Using AES, you should not use the same IV twice with the same key, which is why cryptosystems typically generate a key and an initial IV uniquely during the key exchange, and then use something (like a message sequence number) to change the IV for each message that's encrypted with that one key.
For test purposes, like yours, you might want to generate a fixed 16 bit key and fixed 16 bit IV using a random number generator like this. Then use the .fromBase64() constructors.
var key = Key.fromBase64('yE9tgqNxWcYDTSPNM+EGQw=='); // obviously, insert your own value!
var iv = IV.fromBase64('8PzGKSMLuqSm0MVbviaWHA==');
Use the same values for key and IV in the encrypting and decrypting app.
You can use this method:
String encrypted = "68e4sg68es4ges68g4";
final decrypted = encrypter.decrypt(encrypt.Encrypted.fromBase64(encrypted),iv:iv);
I'm working on the porting of an instance messaging application from Java to JavaME ,that also implements cryptography. The problem is that I want to send my public key to the server. The desktop client has this code for this job:
byte[] encoded_public_key=publick_key.getEncoded();
And the server has this code to retrieve the key:
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded_public_key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey puKey = keyFactory.generatePublic(publicKeySpec);
Now I've looked the API for the getEncoded and it says that it returns the DER-encoded form of the key as a byte array (http://www.docjar.com/docs/api/sun/s...tml#getEncoded)
My implementation for that in JavaME was this:
RSAPublicKeyStructure public_key_JAVAME=new RSAPublicKeyStructure(modulus,exponent);
byte[] DER_encoded_public_key_JAVAME=public_key_JAVAME.getDEREncoded();
//the getEncoded functions returns exact the same byte array.
However when I try to retrieve the JavaME created DER encoded key with the server code ,in other words when I try this:
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(DER_encoded_public_key_JAVAME);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey puKey = keyFactory.generatePublic(publicKeySpec);
I get
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:188)
at java.security.KeyFactory.generatePublic(KeyFactory.java:304)
Caused by: java.security.InvalidKeyException: IOException: algid parse error, not a sequence
at sun.security.x509.X509Key.decode(X509Key.java:380)
at sun.security.x509.X509Key.decode(X509Key.java:386)
at sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:66)
at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:281)
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:184)
Interesting point : The DER encoded key from the normal Java (using the getencoded() function) is a byte array is 162 bytes long while the SAME key DER encoded in JavaME using bouncy castle is 140 bytes long. Shouldn't these 2 DER encoded key be of the same lenght?I mean it's the same key in DER encoded format so they should be the same.
What am I doing wrong?
True I didn't notice that.Problem is do you know how to create a subjectPublickeyInfo object from a PublicKey in bouncyCastle? I've tried:
ByteArrayInputStream bIn = new ByteArrayInputStream(RSApublickey.toString().getbytes());
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(bIn).readObject());
But it didn't work. I also tried :
ByteArrayInputStream(RSApublicKeyStructure.getEncoded());
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(bIn).readObject());
Actually I did expect that not to work , but well I had to try it . So how can I create a Subjectpublickeyinfo from RSAkeyparameters?( This is one of the points where the obscurity of bouncy's castle API really shines I guess)
Again thank you for your response you've been of great help.You've put me on the right track.
DER-encoding is just a standard for encoding. Saying that a key is DER-encoded is equivalent to saying it is XML-encoded: you need to agree on how it is DER-/XML-encoded to be able to decode it.
In this case your RSAPublicKeyStructure.getEncoded() returns the key as the DER-encoding of an ASN.1 RSAPublicKey:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
The X509EncodedKeySpec on the other hand expects to be handed the DER-encoding of an ASN.1 PublicKeyInfo:
PublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
PublicKey BIT STRING
}
To create a PublicKeyInfo using BouncyCastle do this (courtesy of GregS):
RSAPublicKeyStructure rsaPublicKey = /* ... */
AlgorithmIdentifier rsaEncryption = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo(rsaEncryption, rsaPublicKey);
byte[] encodedPublicKeyInfo = publicKeyInfo.getEncoded();
I have a large database of users (~200,000) that I'm transferring from a ASP.NET application to a Ruby on Rails application. I don't really want to ask every user to reset their password and so I'm trying to re-implement the C# password hashing function in Ruby.
The old function is this:
public string EncodePassword(string pass, string saltBase64)
{
byte[] bytes = Encoding.Unicode.GetBytes(pass);
byte[] src = Convert.FromBase64String(saltBase64);
byte[] dst = new byte[src.Length + bytes.Length];
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inArray = algorithm.ComputeHash(dst);
return Convert.ToBase64String(inArray);
}
An example hashed password and salt is (and the password used was "password"):
Hashed password: "weEWx4rhyPtd3kec7usysxf7kpk="
Salt: "1ptFxHq7ALe7yXIQDdzQ9Q=="
Password: "password"
Now with the following Ruby code:
require "base64"
require "digest/sha1"
password = "password"
salt = "1ptFxHq7ALe7yXIQDdzQ9Q=="
concat = salt+password
sha1 = Digest::SHA1.digest(concat)
encoded = Base64.encode64(sha1)
puts encoded
I'm not getting the correct password hash (I'm getting "+BsdIOBN/Vh2U7qWG4e+O13h3iQ=" instead of "weEWx4rhyPtd3kec7usysxf7kpk="). Can anyone see what the problem might be?
Many thanks
Arfon
Just a quick update, a colleague of mine has solved this:
require "base64"
require "digest"
require "jcode"
def encode_password(password, salt)
bytes = ""
password.each_char { |c| bytes += c + "\x00" }
salty = Base64.decode64(salt)
concat = salty+bytes
sha1 = Digest::SHA1.digest(concat)
encoded = Base64.encode64(sha1).strip()
puts encoded
end
I have been tasked with migrating an existing .NET app to Ruby on Rails. I am using the code below to mimic the .NET password hashing. I am very new to Ruby, and don't know .NET at all. The code may not be as clean as it could, but it's a start.
To test, save this as a Ruby script and run with:
ruby script plain_text_password salt_in_base64
e.g.
ruby dotNetHash.rb password123 LU7hUk4MXAvlq6DksvP9SQ==
require "base64"
require "digest"
# Encode password as double-width characters
password_as_text = ARGV.first
double_width_password = []
double_width_password = password_as_text.encode("UTF-16LE").bytes.to_a
# Unencode the salt
salt = Base64.decode64(ARGV[1])
# Concatenate salt+pass
salt_pass_array = []
salt_pass_array = salt.bytes.to_a + double_width_password
# Repack array as string and hash it. Then encode.
salt_pass_str = salt_pass_array.pack('C*')
sha1_saltpass = Digest::SHA1.digest(salt_pass_str)
enc_sha1_saltpass = Base64.encode64(sha1_saltpass).strip()
puts "Encoded SHA1 saltpass is " + enc_sha1_saltpass
You are pretty close. Unfortunately Ruby has no built-in unicode support at the moment, and your hashing function relies on it. There are workarounds. Look around the site on how to do unicode in Ruby.
BTW, I think you forgot to base64 decode the salt, it looks like the ASP.net function does that.
You need to unencode the salt to convert it back to it's byte representation and then concatenate that with the password to get the hashed password value. You're using the encoding salt string directly (which is a different salt) and thus it is hashing to something different.
require "base64"
require "digest/sha1"
password = "password"
salt = Base64.decode64("1ptFxHq7ALe7yXIQDdzQ9Q==")
concat = salt+password
sha1 = Digest::SHA1.digest(concat)
encoded = Base64.encode64(sha1)
puts encoded