I have this requirement where I need to encrypt a dictionary in python using password based AES256 encryption. I was only given a sample vb.net project by my boss to take reference from regarding the steps that I need to follow for encryption but I am not really aware of how vb.net works. I have converted as much code from vb.net to python as much I could but still the output that my code produces is different from the vb.net project. Can someone please explain what exactly am I missing out on due to which the encryption output of my python program is different than the encryption output of the vb.net program? Thanks in advance!
Here is the vb.net code
Imports System.IO
Imports System.Security.Cryptography
Imports System.Text
Imports System.Web.Script.Serialization
Imports System.Net
Imports System.Security.Cryptography.X509Certificates
Imports System.Net.Security
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
loginrequest()
End Sub
Private Sub loginrequest()
Dim lobjLogin As New loginrequest
lobjLogin.jsonProperty1 = "jsonProperty1"
lobjLogin.jsonProperty2 = "jsonProperty2"
lobjLogin.jsonProperty3 = "jsonProperty3"
lobjLogin.jsonProperty4 = "jsonProperty4"
Dim lRequestJson As String = ""
lRequestJson = (New JavaScriptSerializer()).Serialize(lobjLogin)
Dim lchecksum As String = EncryptText(lRequestJson, "key")
End Sub
Public Function EncryptText(pInput As String, password As String) As String
Dim bytesToBeEncrypted As Byte() = Encoding.UTF8.GetBytes(GenerateSHA256String(pInput))
Dim passwordBytes As Byte() = Encoding.UTF8.GetBytes(password)
passwordBytes = SHA256.Create().ComputeHash(passwordBytes)
Dim bytesEncrypted As Byte() = AES_Encrypt(bytesToBeEncrypted, passwordBytes)
Dim result As String = Convert.ToBase64String(bytesEncrypted)
Return result
End Function
Private Function AES_Encrypt(bytesToBeEncrypted As Byte(), passwordBytes As Byte()) As Byte()
Dim encryptedBytes As Byte() = Nothing
Dim saltBytes As Byte() = New Byte() {1, 2, 3, 4, 5, 6, _
7, 8}
Using ms As New MemoryStream()
Using AES As New RijndaelManaged()
AES.KeySize = 256
AES.BlockSize = 128
Dim key = New Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000)
AES.Key = key.GetBytes(AES.KeySize / 8)
AES.IV = key.GetBytes(AES.BlockSize / 8)
AES.Mode = CipherMode.CBC
Using cs = New CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write)
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length)
cs.Close()
End Using
encryptedBytes = ms.ToArray()
End Using
End Using
Return encryptedBytes
End Function
Private Function GenerateSHA256String(ByVal inputString) As String
Dim sha256 As SHA256 = SHA256Managed.Create()
Dim bytes As Byte() = Encoding.UTF8.GetBytes(inputString)
Dim hash As Byte() = sha256.ComputeHash(bytes)
Dim stringBuilder As New StringBuilder()
For i As Integer = 0 To hash.Length - 1
stringBuilder.Append(hash(i).ToString("X2"))
Next
Return stringBuilder.ToString()
End Function
End Class
Public Class loginrequest
Public Property jsonProperty1 As String
Public Property jsonProperty2 As String
Public Property jsonProperty3 As String
Public Property jsonProperty4 As String
End Class
Here is the corresponding python code that I wrote
import base64
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Util.Padding import pad
import hashlib, json
payload = {
"jsonProperty1": "jsonProperty1",
"jsonProperty2": "jsonProperty2",
"jsonProperty3": "jsonProperty3",
"jsonProperty4": "jsonProperty4",
}
payload = json.dumps(payload)
bytesToBeEncrypted = hashlib.sha256(payload.encode("utf-8")).hexdigest()
class AESCipher(object):
def __init__(self, key, interactions=1000):
self.bs = AES.block_size
self.key = hashlib.sha256(key.encode("utf-8")).hexdigest()
self.interactions = interactions
def pkcs7padding(self, data, block_size=16):
if type(data) != bytearray and type(data) != bytes:
raise TypeError("Only support bytearray/bytes !")
pl = block_size - (len(data) % block_size)
return data + bytearray([pl for i in range(pl)])
def encrypt(self, raw):
import os
raw = "".join([x.upper() for x in bytesToBeEncrypted])
keyiv = PBKDF2(self.key, os.urandom(8), 48, self.interactions)
key = keyiv[:32]
iv = keyiv[32:48]
cipher = AES.new(key, AES.MODE_CBC, iv)
encoded = raw.encode("utf-8")
encodedpad = self.pkcs7padding(encoded)
ct = cipher.encrypt((encodedpad))
cip = base64.b64encode(ct)
print(cip, len(cip))
enc = AESCipher("key")
dec = enc.encrypt(bytesToBeEncrypted)
Please note that I took some reference from some other threads as well regarding my python code because encryption is a new concept for me.
P.S. I also found out that the vb.net code is using .toString("X2") to generate a hexadecimal string in uppercase but unfortunately, I was not able to find the corresponding equivalent in python for the same. Could that be a problem?
Related
Does anyone know how the Terra Station Wallet generates the 364 characters Private Key? I am looking for a way to generate this 364 characters Private Key using terra-sdk, but the length of the mk = MnemonicKey()'s mk.private_key is not 364 characters.
Appreciate any help
You sparked my curiosity. I went through the Terra Station code (I chose mobile) to see how they do it. I first searched for where in the UI was the “Export Private Key”; it looks to be the encrypted Key string in AuthDataValueType.
Here’s where they read it out of keystore. https://github.com/terra-money/station-mobile/blob/f74c4224986fd9ed32b4380b537e9ae13ca05c3e/src/utils/authData.ts#L15
Here’s where they create it for a newly recovered wallet. https://github.com/terra-money/station-mobile/blob/3ec15b9a620432dee47378f5b6e621d93780748a/src/utils/wallet.ts#L66
And, lastly here are the encrypt util functions. https://github.com/terra-money/station-mobile/blob/3ec15b9a620432dee47378f5b6e621d93780748a/src/utils/crypto.ts
This is all NodeJS/ReactNative code so you would need to create the same encrypt/decrypt, password and storage flow in Python, if necessary.
The import 'key' for Terra Station is actually a base64 encoded JSON object containing the wallet name, Terra address and the private key (which is further AES encrypted and base64 encoded). This is some C# to create it (you will need to get the private key using something like the Mnemonic Code Converter webpage) - fill in the string variables at the top:
string privatekey = #"";
string walletName = #"";
string address = #"";
string password = #"changeme";
byte[] salt = Encoding.UTF8.GetBytes("kopwemdmondawfwa");
byte[] iv = Encoding.UTF8.GetBytes("dgfdkfsokwedopmf");
int iterations = 100;
int keySize = 256;
var myRijndael = new RijndaelManaged();
myRijndael.KeySize = keySize;
myRijndael.IV = iv;
var rfc2898 = new Rfc2898DeriveBytes(System.Text.Encoding.UTF8.GetBytes(password), salt, iterations);
byte[] key = rfc2898.GetBytes(keySize / 8);
myRijndael.Key = key;
myRijndael.Padding = PaddingMode.PKCS7;
myRijndael.Mode = CipherMode.CBC;
ICryptoTransform transform = myRijndael.CreateEncryptor();
byte[] bak = new System.Text.UTF8Encoding().GetBytes(privatekey);
byte[] encrypted = transform.TransformFinalBlock(bak, 0, bak.Length);
string saltStr = BitConverter.ToString(salt).Replace("-", "");
string ivStr = BitConverter.ToString(iv).Replace("-", "");
string cipherStr = System.Convert.ToBase64String(encrypted);
string keyString = saltStr + ivStr + cipherStr;
string res = "{ \"name\":\"" + walletName + "\",\"address\":\"" + address + "\",\"encrypted_key\":\"" + keyString + "\"}";
byte[] resBytes = new System.Text.UTF8Encoding().GetBytes(res);
string base64 = System.Convert.ToBase64String(resBytes);
File.WriteAllText(#"c:\temp\ts_exported_key.txt", base64);
<?php
$password = "SECRETPASSWORD";
$nonce = random_bytes(32); # requires PHP 7
date_default_timezone_set("UTC");
$timestamp = date(DATE_ATOM);
$encodedNonce = base64_encode($nonce);
$passSHA = base64_encode(sha1($nonce . $timestamp . sha1($password, true), true));
?>
it generates the below result with a 28 characters password digest, which I am using in soap requests, and it works fine
password_digest = '/pBYmwwc2cM87CUr8oB4Wkmyc0Q='
nonce = '���>�!��g��q�[�`�R��=J�o�'
nonce_base_64_encode = 'uqbkProhsR3JZxjC93HWW8BghQFSgqo9Sv9vGgUa4hs='
timestamp = '2022-01-13T18:28:52+00:00'
I need to do this same in python, but python is somehow generating longer password_digest and the soap request fails. I don't know if I am not generating the random_bytes correctly in python or some other issue. Below is python code:
import secrets
import hashlib
import datetime
clearPassword = 'MYSECRETPASSWORD'
created_at_timestamp_utc = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
def getSha1String(string):
stringInBytes = string.encode('ascii')
hash_object = hashlib.sha1(stringInBytes)
hex_dig = hash_object.hexdigest()
return hex_dig
def getBase64NonceString(nonce):
nonce_bytes = nonce.encode('ascii')
base64_bytes = base64.b64encode(nonce_bytes)
base64_nonce = base64_bytes.decode('ascii')
return base64_nonce
def getBase64String(string):
string_bytes = string.encode('ascii')
base64_bytes = base64.b64encode(string_bytes)
base64_string = base64_bytes.decode('ascii')
return base64_string
nonce = secrets.token_bytes(32)
base64_nonce = getBase64Nonce(nonce)
sha1_password = getSha1String(clearPassword)
password_digest = getBase64String(getSha1String(str(nonce) + created_at_timestamp_utc + sha1_password))
Your python code has 3 problems:
You're using binary output from sha1() in PHP, but hex output in python. Use digest(), not hexdigest(). This is why your output is longer in python.
Your timestamp format is incorrect.
The PHP format DATE_ATOM is "Y-m-d\TH:i:sP", where P outputs the UTC offset in the format +00:00. Unfortunately Python's strftime() doesn't seem to have an equivalent, but it's all in UTC anyway and your python code simply specifies the static string Z. So change that to +00:00, otherwise your tokens won't match.
Eg: timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S+00:00')
You're using SECRETPASSWORD in PHP and MYSECRETPASSWORD in python, and I am emabarrased at how long I bashed my head against that one without noticing.
Succint working code:
import hashlib, datetime, base64
password = 'SECRETPASSWORD'
timestamp = '2022-01-13T18:28:52+00:00'
nonce = base64.b64decode('uqbkProhsR3JZxjC93HWW8BghQFSgqo9Sv9vGgUa4hs=')
def quickSHA(input):
return hashlib.sha1(input).digest()
def makeToken(password, timestamp, nonce):
return base64.b64encode(
quickSHA( nonce + timestamp + quickSHA(password) )
)
print makeToken(password, timestamp, nonce)
Output: /pBYmwwc2cM87CUr8oB4Wkmyc0Q=
I have this encrypt with AES function in python:
def encrypt_text(self):
raw = self.PKCS7_padding()
iv = Random.new().read(self.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
encrypted = base64.b64encode(iv + cipher.encrypt(raw))
return encrypted
And then padding it with:
def PKCS7_padding(self):
return self.text+chr(16-len(self.text)%16)*(16-len(self.text)%16)
But when I send it to this c# function
public static string DecryptString(string key, string cipherText)
{
byte[] iv = new byte[16];
byte[] buffer = Convert.FromBase64String(cipherText);
using (Aes aes = Aes.Create())
{
aes.Key = Convert.FromBase64String(key);
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream(buffer))
{
using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
Say i send "hello world"
I get a weird mix of the string i sent plus random non pritable characters. EX: "�Ȯ�Ŗf�"��Xhello world"
The key is generated as follows:
key = base64.b64encode(os.urandom(32))
return key.decode()
I do not get any errors but weird text any ideas Thanks! If you have any questions please feel free to ask
In the Python code, a random IV is generated and concatenated with the ciphertext. However, in the C# code, the separation of IV and ciphertext is missing. Instead, a zero IV is applied and the concatenation of IV and ciphertext is decrypted, which causes the problem.
The separation of IV and ciphertext can be added to the C# code e.g. as follows:
byte[] buffer = ...
byte[] ct = new byte[buffer.Length - iv.Length];
Buffer.BlockCopy(buffer, 0, iv, 0, iv.Length);
Buffer.BlockCopy(buffer, iv.Length, ct, 0, ct.Length);
buffer = ct;
using ...
Note that PyCryptodome supports padding in a dedicated module: Crypto.Util.Padding.
Also, in the Python code the Base64 encoded key is nowhere Base64 decoded (but maybe the corresponding code was just not posted).
I have some already encrypted data which needs to be decrypted using python. The decryption logic in C# looks as given below.
using System.Security.Cryptography;
private const string Url_ENCRYPTION_KEY = "abcd123456";
private readonly static byte[] URL_SALT = Encoding.ASCII.GetBytes(Url_ENCRYPTION_KEY.Length.ToString());
public static string Decrypt(string inputText) {
try {
if (!string.IsNullOrEmpty(inputText)) {
RijndaelManaged rijndaelCipher = new RijndaelManaged();
byte[] encryptedData = Convert.FromBase64String(inputText.Replace(" ", "+"));
PasswordDeriveBytes secretKey = new PasswordDeriveBytes(Url_ENCRYPTION_KEY, URL_SALT);
using (ICryptoTransform decryptor =
rijndaelCipher.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16))) {
using (MemoryStream memoryStream = new MemoryStream(encryptedData)) {
using (CryptoStream cryptoStream =
new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) {
byte[] plainText = new byte[encryptedData.Length];
int decryptedCount = cryptoStream.Read(plainText, 0, plainText.Length);
return Encoding.Unicode.GetString(plainText, 0, decryptedCount);
}
}
}
}
}
catch(Exception ex) {
//clsErrorHandler.LogError(ex);
}
return inputText;
}
I have tried libs like pprp and python's cryptography but the solutions out there use PBKDF2, while the C# code here supplies the decryptor bytes of key and salt as key and IV values.
From what I looked the PasswordDeriveBytes function basically work as a somewhat modified PBKDF1,
but all of the solutions I tried fail with somekind of esoteric this size doesn't match with that size errors.
Here is one implementation of PasswordDeriveBytes I found floating out there but I am at a loss on how to do something similar to secretKey.GetBytes(32) and creating a decryptor
import hashlib
from base64 import b64decode
def MS_PasswordDeriveBytes(pstring, salt, hashfunc, iterations, keylen):
if iterations > 0:
lasthash = hashlib.sha1(pstring+salt).digest()
iterations -= 1
else:
print("Iterations must be > 0")
#If iterations is 1 then basically the same thing happens as 2 based on my testing
#if iterations == 0 and keylen > len(lasthash):
#print("Dunno what happens here")
#return -1
for i in range(iterations-1):
lasthash = hashlib.sha1(lasthash)
bytes = hashlib.sha1(lasthash).digest()
ctrl = 1
while len(bytes) < keylen:
bytes += hashlib.sha1(str(ctrl)+lasthash).digest()
ctrl += 1
return(bytes[:keylen])
stpass = 'amp4Z0wpKzJ5Cg0GDT5sJD0sMw0IDAsaGQ1Afik6NwXr6rrSEQE='
slt = 'aGQ1Afik6NampDT5sJEQE4Z0wpsMw0IDAD06rrSswXrKzJ5Cg0G='
initv = '#1B2c3D4e5F6g7H8'
enc_str = b64decode('B5YDTLEDBjd+8zy5lzEfjw==')
derbytes = MS_PasswordDeriveBytes(stpass, slt, hashlib.sha1, iterations=2, keylen=32)
I have a Kamstrup WMbus water meter sending out frames like this:
21442D2C529027581B168D2814900939201F0775C6452FBBAC155B46A546035219D51AB8
I am trying to decrypt this in Python.
Breaking the frame up I have the following values:
Key 16ce383ebc0790e928215525cd4f7abf
Input IV 2D2C529027581B162890093920000000
Input Data 1F0775C6452FBBAC155B46A546035219D5
Pasting this into http://www.cryptogrium.com/aes-ctr.html results in a valid decrypted frame:
bbe57934ddc46a71004400000044000000
I have tried both PyCrypto and PyCryptodome but neither gives the same correct answer than cryptogrium.com.
from Cryptodome.Cipher import AES
# ctr = Crypto.Util.Counter.new(128, initial_value=int("0000002039099028161B582790522C2D", 16))
# cipher = Crypto.Cipher.AES.new(ecryptionKey, Crypto.Cipher.AES.MODE_CTR, counter=ctr)
# secret = Secret()
spec = AES.new(ecryptionKey, AES.MODE_CTR, iv=inputIV)
dataDec = cipher.decrypt(data)
The first commented out approach runs but gives the wrong result.
The second one stops with the error:
TypeError: Invalid parameters for CTR mode: {'iv': bytearray(b"-,R\x90\'X\x1b\x16(\x90\t9 \x00\x00\x00")}
In C# we are using the following which works:
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
ParametersWithIV ivAndKey = new ParametersWithIV(new KeyParameter(keyBytes), inputIVBytes);
cipher.Init(false, ivAndKey);
...
int length1 = cipher.ProcessBytes(data, 0, data.Length, outBuf, 0);
int length2 = cipher.DoFinal(outBuf, length1);
...
I am confused because C# uses the parameters I have: key, data, IV
But Python expects: key, data, counter
Does anyone have an example how I can decrypt this in Python3? Or maybe explain how I should use the IV to set up the counter for AES-CNT?
In the end one of the examples from https://www.programcreek.com/python/example/87998/Crypto.Cipher.AES.MODE_CTR put me on the right track. After fixing a couple of typos in my code it worked correctly.
def kamstrupDecrypt(meterSerial, frame, payload):
inputIV = getInputIV(frame)
print("IV:", binascii.hexlify(inputIV))
ecryptionKey = getEncryptionKey(meterSerial)
print("Key:", binascii.hexlify(ecryptionKey))
counter = Counter.new(128, initial_value = bytes_to_long(inputIV))
cipher = AES.new(ecryptionKey, AES.MODE_CTR, counter=counter)
payloadDec = cipher.decrypt(payload)
print("Decrypted: ", binascii.hexlify(payloadDec))
return payloadDec