I was trying to convert a java code to python. tried to use pycryptodome library for the purpose. This is java code:
try {
String data= "shouldEncryptDATA";
String bas64ed= "";
int len12 = Integer.parseInt("12");
byte[] randomparam= new byte[len12];
new SecureRandom().nextBytes(randomparam);
SecretKeySpec secretKeySpec= new SecretKeySpec("1f1f7892GKG38815".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(1, secretKeySpec, new GCMParameterSpec(Integer.parseInt("16") * 8, randomparam));
byte[] bytedata= cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] newbytearray= new byte[bytedata.length + len12];
System.arraycopy(randomparam, 0, newbytearray, 0, len12);
System.arraycopy(bytedata, 0, newbytearray, len12, bytedata.length);
bas64ed= Base64.getEncoder().encodeToString(newbytearray);
System.out.println("bas64ed: "+bas64ed);
System.out.println(URLEncoder.encode(bas64ed, "UTF-8"));
} catch (Exception unused_ex) {
System.out.println();
System.out.println("ERROR: " + unused_ex);
}
so far I tried below python code to mimic above java code:
import base64
from Crypto.Cipher import AES
import urllib.parse
from Crypto.Random import get_random_bytes
data= "shouldEncryptDATA"
key = b '1f1f7892GKG38815'
len12 = 12
v1 = bytearray(len12)
cipher = AES.new(key, AES.MODE_GCM, nonce=get_random_bytes(12) )
ciphertext, tag = cipher.encrypt_and_digest(data.encode("utf8"))
base64ed = ""
for x in cipher.nonce, ciphertext, tag:
base64ed += base64.b64encode(x).decode('utf-8')
urlencoded = urllib.parse.quote_plus(base64ed+"\n")
print(urlencoded )
the above python code generates an output but the problem is that when I supply the output to the decryption code I got MAC check Failed. so I realized my implementation in the python code is somewhat is the problem because the output of the Java code when passed to the decryption code, it works and decrypts the data. so How can I correctly convert the Java encryption code to Python? using the Pycryptodome or any other suitable library.
The bug in the Python code is in the concatenation, it must be:
base64ed = base64.b64encode(cipher.nonce + ciphertext + tag).decode('utf-8')
After this fix, if you compare the ciphertexts of both codes with identical input data (especially with identical IV), they are the same.
Related
Is it possible to implement like this technique in java (encryption and Decryption) using python ?
Here the 3DES is using in this java code.
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
String keytext = "key......";
byte[] keyArray = keytext.getBytes("utf-8");
SecretKey key = new SecretKeySpec(keyArray, "DESede");
String mytext = "anytext......";
Cipher.init(1, key);
byte[] enc = cipher.doFinal(plaintext.getBytes("utf-8"));
String value = Base64.getUrlEncode().encodeToString(enc);
return value;
Hope someone can assist me with this, to implement this using python2 if possible or python3
EDIT:
from Crypto.Cipher import DES3
from Crypto.Random import get_random_bytes
while True:
try:
key = DES3.adjust_key_parity(get_random_bytes(24))
break
except ValueError:
pass
cipher = DES3.new(key, DES3.MODE_CFB)
plaintext = b'We are no longer the knights who say ni!'
msg = cipher.iv + cipher.encrypt(plaintext)
print(msg)
Taken from the PyCryptodome manual itself: Link
I have Java based Web API which receives AES encrypt user credentials and let the user get the session token. This is working fine with AES encryption on client side with CryptoJS. But the same throws bad padding exception while trying to access this API from Python.
In Java I've used PKCS5Padding padding. I have tried this with NoPadding as well, but in that case the de-crypted text is simply empty. Also, when trying with NoPadding, CryptoJS code is not working properly. As, this API should work for any client side program (Later I'm planning to expand this to PHP, .Net and R as well), I want to have a common, working mechnism for encryption of passwords thru AES. I'm pretty new to python and I have tried various programs provided in stack overflow as well as in other blogs on this subject.
Here is the java part
public String decrypto(String cipherText, String secret) {
byte[] cipherData = java.util.Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
String decryptedText = null;
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8),
md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
And this line send the encrypted password from CrptoJS without any issues which gets de-crypted properly in Java
encKey = CryptoJS.AES.encrypt(this.logingrp.value['loginpass'], this.localPublicKey).toString();
this.localLogin(encKey);
LocalLogin is an AJAX function to call the API
Python code is below. I'm using Pycryptodome
import requests, json
import base64
from Crypto.Cipher import AES
from Crypto.Util import Padding
from Crypto import Random
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from hashlib import sha256
BS = 16
pad = lambda s: bytes(s + (BS - len(s) % BS) * chr(BS - len(s) % BS), 'utf-8')
unpad = lambda s : s[0:-ord(s[-1:])]
class AESCipher:
def __init__( self, key ):
self.key = bytes(key, 'utf-8')
def encrypt( self, raw ):
raw = pad(raw)
iv = Random.new().read( AES.block_size )
cipher = AES.new(self.key, AES.MODE_CBC )
return base64.b64encode( iv + cipher.encrypt( raw ) )
def decrypt( self, enc ):
enc = base64.b64decode(enc)
iv = enc[:16]
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc[16:] )).decode('utf8')
header = {"Content-type": "application/json",
"Authorization": "Bearer CT9797"
}
password = 'stone#123'
keyphrase = '9DA538D0HMQXQRGI';
cipher = AESCipher(keyphrase)
encrypted = cipher.encrypt(password).decode('utf8')
payload={'userId':'CT9797','userData':encrypted,'encKey':''}
response_decoded_json = requests.post(url=_base_URL+'eLogin', data=json.dumps(payload), headers=header)
print(response_decoded_json.status_code)
The below exception occurs while trying the above program on java side
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:10
Appreciate your time in reading and helping me out.
I have set up a server that is listening for an encrypted byte array through the java function mentioned below. Earlier i was using java (android) to build my app so it was easy using the same java function but i am unable to figure out what is the dart equivalent(flutter) of that function which takes a string as input and returns an AES encrypted byte array as output ,which i can write to the tcp socket .
Also i'd really appreciate help on knowing how to write the generated byte array to the server and then read a similar response and decrypting it through dart (flutter)
I have been successful in writing simple strings and receiving simple strings as input and output to the tcp server through dart but cant do the same for encrypted byte arrays . In java i used DataOutputStream to send the response to server like this
DataOutputStream dOut = newDataOutputStream(socket.getOutputStream());
byte[] s2 = Encrypt3.encrypt2(myString);
dOut.writeInt(s2.length); // write length of the message
dOut.write(s2);
Here is the java function i used for aes encryption
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class Encrypt3 {
public static String key = "mykey";
public static byte[] encrypt2(String text ){
String encrypt ="";
try{
// Create key and cipher
Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
// encrypt the text
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encrypted = cipher.doFinal(text.getBytes());
return encrypted ;
}catch(Exception e){
System.out.println(e);
}
return null ;
}
public static String decrypt2(byte[] encrypted2){
String decrypt ="";
try{
Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
// decrypt the text
cipher.init(Cipher.DECRYPT_MODE, aesKey);
decrypt = new String(cipher.doFinal(encrypted2));
}catch(Exception e){
System.out.println(e);
}
return decrypt ;
}
}
I expect the equivalent bytearray to be produced in dart when provided with a string as input,and then writing the byte array to a tcp server.
Thank You in advance
The way you are configuring the Java cipher defaults to Electronic Code Book (ECB) mode which shouldn't be used in a real cipher system. You are also relying on the default padding - PKCS5.
Use the pointycastle package, with the following imports:
import 'package:pointycastle/api.dart';
import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/padded_block_cipher/padded_block_cipher_impl.dart';
import 'package:pointycastle/block/modes/ecb.dart';
An approximate equivalent in Dart is:
List<int> encrypt2(String text) {
// key must be 16 or 32 bytes - not sure how "mykey" could work
// key should really be binary, not a String! Better to use a KDF.
Uint8List key = Uint8List.fromList(
utf8.encode('0123456789abcdef'),
);
PaddedBlockCipher cipher = PaddedBlockCipherImpl(
PKCS7Padding(), // Java defaults to PKCS5 which is equivalent
ECBBlockCipher(AESFastEngine()), // Very weak mode - don't use this in the real world
);
cipher.init(
true,
PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
KeyParameter(key),
null,
),
);
return cipher.process(utf8.encode(text)); // this isn't the same as .toBytes, except for ASCII
}
I have encrypted a message using AES/GCM/NoPadding algorithm(AES-256) in java & trying to decrypt it in NodeJs. Getting exception "Error: Unsupported state or unable to authenticate data" while decryption. Below is the complete code of java and nodejs & error message:
Pl help me where is the incorrect code in java or nodejs.
Below is the code started with Java encryption code :
public static String encryptAES(String privateString, String skey) throws Exception{
byte[] iv = new byte[GCM_IV_BYTES_LENGTH]; //12 iv length
byte[] tag = new byte[GCM_TAG_BYTES_LENGTH]; //16 tag length
(new SecureRandom()).nextBytes(iv);
(new SecureRandom()).nextBytes(tag);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); //algorithm type
GCMParameterSpec ivSpec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * Byte.SIZE, iv);
cipher.init(Cipher.ENCRYPT_MODE, getKey(skey), ivSpec);
byte[] ciphertext = cipher.doFinal(privateString.getBytes("UTF8"));
byte[] ivTag = new byte[GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH]; // merging iv and tag
System.arraycopy(iv, 0, ivTag, 0, iv.length);
System.arraycopy(tag, 0, ivTag, iv.length, tag.length);
byte[] encrypted = new byte[ivTag.length + ciphertext.length]; //merging ivtag and cipher
System.arraycopy(ivTag, 0, encrypted, 0, ivTag.length);
System.arraycopy(ciphertext, 0, encrypted, ivTag.length, ciphertext.length);
String encoded = Base64.getEncoder().encodeToString(encrypted); //b64 encoded value
System.out.println("encrypted str:>" + encoded.length() + " | " + encoded);
return encoded;
}
//NodeJS decryption code :
function decryptTokenResponse(encryptedStr){
let data = encryptedStr
const bData = Buffer.from(data, 'base64');
const iv = bData.slice(0, 12);
const tag = bData.slice(12, 28);
const text = bData.slice(28);
var decipher = crypto.createDecipheriv(algorithm,masterkey, iv)
decipher.setAuthTag(tag)
var plainText = decipher.update(text,'base64','utf-8');
plainText += decipher.final('utf-8'); **//getting exception here**
console.log('Decrypted data = ' + plainText)
}
**//Error :**
internal/crypto/cipher.js:145
const ret = this._handle.final();
^
Error: Unsupported state or unable to authenticate data
at Decipheriv.final (internal/crypto/cipher.js:145:28)
at decryptTokenResponse (/home/jdoodle.js:40:27)
at Object.<anonymous> (/home/jdoodle.js:18:1)
at Module._compile (internal/modules/cjs/loader.js:678:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
at Module.load (internal/modules/cjs/loader.js:589:32)
at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
at Function.Module._load (internal/modules/cjs/loader.js:520:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)
at startup (internal/bootstrap/node.js:228:19)
Command exited with non-zero status 1
The authtag for GCM or CCM is generated by the encrypt operation -- you do not randomly generate it yourself (as you do, or at least can, for the IV/nonce). However it is sort of hidden, because Java crypto fits authenticated encryption into its preexisting API by appending the tag to the ciphertext returned by an encrypt operation, or input to a decrypt operation. OTOH nodejs/OpenSSL treats them as separate values. (Both Java and nodejs/OpenSSL treat AAD as separate, but you aren't using AAD.)
Since you are already packing things together (and base64ing) for transmission, you should:
in Java, concatenate the IV plus the return from cipher.doFinal (which is ctx + tag) forming IV + ctx + tag
base64 and send and after receiving de-base64 as you already do
in nodejs, split these into IV,ctx,tag which is easy because Buffer can slice from both ends: bData.slice(0,12) bData.slice(12,-16) bData.slice(-16)
Also your text is already de-base64-ed, but since it's a Buffer the inputEncoding to decipher.update is ignored.
You must provide the AuthenticationTag to createDeCipherIv() when using AES in GCM, CCM and OCBmodes.
Why would you implement GCM without it? You may as well use CTR mode of AES if you don't want the additional protections.
I some how figured it out that how it would work for me, I was getting the same error then observing it again and again for same encryption I found that whenever the encrypted output contains "/" or "+" character I get this same error. Then I used while loop to keep on encrypting again and again untill output doesn't contain there characters, and give the output which does not has any of these two characters. It worked for me this way.
Hope that It would work for you as well.
I'm tasked with building a consumer of an API that requires an encrypted token with a seed value that is the UNIX time. The example I was shown was implemented using Java which I'm unfamiliar with, and after reading through documentation and other stack articles have been unable to find a solution.
Using the javax.crypto.SecretKey, javax.crypto.SecretKeyFactory, javax.crypto.spec.PBEKeySpec, and javax.crypto.spec.SecretKeySpec protocols, I need to generate a token similar to the below:
public class EncryptionTokenDemo {
public static void main(String args[]) {
long millis = System.currentTimeMillis();
String time = String.valueOf(millis);
String secretKey = "somekeyvalue";
int iterations = 12345;
String iters = String.valueOf(iterations);
String strToEncrypt_acctnum = "somevalue|" + time + "|" + iterations;
try {
byte[] input = strToEncrypt_acctnum.toString().getBytes("utf-8");
byte[] salt = secretKey.getBytes("utf-8");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey tmp = factory.generateSecret(new PBEKeySpec(secretKey.toCharArray(), salt, iterations, 256));
SecretKeySpec skc = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
String query = Base64.encodeBase64URLSafeString(cipherText);
// String query = cipherText.toString();
System.out.println("The unix time in ms is :: " + time);
System.out.println("Encrypted Token is :: " + query);
} catch (Exception e) {
System.out.println("Error while encrypting :" + e);
}
}
}
Should I be using the built-in library hashlib to implement something like this? I can't really find documentation for implementing a PBKDF2 encryption with iterations/salt as inputs. Should I be using pbkdf2? Sorry for the vague questions, I'm unfamiliar with the encryption process and feel like even just knowing what the correct constructor would be is a step in the right direction.
Yes, the Python equivalent is hashlib.pbkdf2_hmac. For example this code:
from hashlib import pbkdf2_hmac
key = pbkdf2_hmac(
hash_name = 'sha1',
password = b"somekeyvalue",
salt = b"somekeyvalue",
iterations = 12345,
dklen = 32
)
print(key)
produces the same key as your Java code.
However, the problem with this code (as mentioned in memo's comment) is the use of salt. The salt should be random and unique for each password. You can create secure random bytes with os.urandom, so a better example would be:
from hashlib import pbkdf2_hmac
from os import urandom
salt = urandom(16)
key = pbkdf2_hmac('sha1', b"somekeyvalue", salt, 12345, 32)
You may also want to increase the number of iterations (I think the recommended minimum number is 10,000).
The rest of the code is easy to 'translate'.
For the timestamp, use time.time to get the current time and multiply by 1000.
import time
milliseconds = str(round(time.time() * 1000))
For encoding you can use base64.urlsafe_b64encode (it includes padding, but you could remove it with .rstrip(b'=')).
Now, for the encryption part, Python doesn't have a built-in encryption module, so you'll have to use a third party library. I recommend pycryptodome or cryptography.
At this point I must warn you that the AES mode you're using is very weak. Please consider using CBC or CTR, or better yet use an authenticated encryption algorithm.