I'm trying to create a hash in Powershell using a key but I seemed to be getting a different result compared when I'm doing it with Python.
Its a signature that I'm generating for an API call.
The Python code I use is as follows:
nonce = "20"
client_id = "mdfgfgkjl3456"
api_key = "asdkjasdkljsomekey"
message = nonce + client_id + api_key
secret = "dsdklfjsdfkljsomesecret"
secret_bytes = bytes(secret , 'latin-1')
message_bytes = bytes(message , 'latin-1')
signature = hmac.new(
secret_bytes,
msg=message_bytes,
digestmod=hashlib.sha256
).hexdigest().upper()
The Powershell Code that I have is as follows:
$nonce = "20"
$clientid = "mdfgfgkjl3456"
$apikey = "asdkjasdkljsomekey"
$message = $nonce + $clientid + $apikey
$secret = "dsdklfjsdfkljsomesecret"
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = [Text.Encoding]::ASCII.GetBytes($secret)
$signature =
$hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($message))
$signature = ([Convert]::ToBase64String($signature)).ToUpper()
$signature
The Powershell runs but it produces a different Signature then the Python code.
In the PowerShell version you perform an additional operation - converting the byte array to a Base64 string - before converting it to upper case:
$signature = ([Convert]::ToBase64String($signature)).ToUpper()
Python on the other hand converts the array to a hexadecimal string.
Change the powershell version to:
$nonce = "20"
$clientid = "mdfgfgkjl3456"
$apikey = "asdkjasdkljsomekey"
$message = $nonce + $clientid + $apikey
$secret = "dsdklfjsdfkljsomesecret"
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = [Text.Encoding]::ASCII.GetBytes($secret)
$signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($message))
$signature = -join($signature |ForEach-Object ToString X2).ToUpper()
$signature
-join($signature |ForEach-Object ToString X2).ToUpper() will produce the exact same format as .hexdigest().upper()
Related
I am trying to transfer a hash key generated encryption via PHP to Python. I couldn't figure out how to edit openssl_encrypt. I would be glad if you help.
PHP Code
function generateSaveCardCreateHashKey(
$merchant_key,
$customer_number,
$card_number,
$card_holder_name,
$expiry_month,
$expiry_year,
$app_secret
){
$data = $merchant_key.'|'. $customer_number .'|'.$card_holder_name.'|'.$card_number.'|'.$expiry_month.'|'.$expiry_year;
$iv = substr(sha1(mt_rand()), 0, 16);
$password = sha1($app_secret);
$salt = substr(sha1(mt_rand()), 0, 4);
$saltWithPassword = hash('sha256', $password . $salt);
$encrypted = openssl_encrypt("$data", 'aes-256-cbc', "$saltWithPassword", null, $iv);
$msg_encrypted_bundle = "$iv:$salt:$encrypted";
$msg_encrypted_bundle = str_replace('/', '__', $msg_encrypted_bundle);
return $msg_encrypted_bundle;
}
Python Code
from Crypto.Hash import SHA1
from Crypto.Hash import SHA256
def generateSaveCardCreateHashKey(
merchant_key ,
customer_number ,
card_number ,
card_holder_name ,
expiry_month ,
expiry_year ,
app_secret
) :
data = merchant_key + ":" + customer_number + "|" + card_holder_name + "|" + card_number + "|" + expiry_month + "|" + expiry_year
randNumIv = str(random.randint(10000000000000000,99999999999999999))
hashNumIv = SHA1.new()
hashNumIv.update(randNumIv.encode("UTF-8"))
hashNumber = hashNumIv.hexdigest()
iv = hashNumber[:16]
hashAppSec = SHA1.new()
hashAppSec.update(app_secret.encode("UTF-8"))
password = hashAppSec.hexdigest()
randNumSalt = str(random.randint(10000000000000000,99999999999999999))
hashNumSalt = SHA1.new()
hashNumSalt.update(randNumSalt.encode("UTF-8"))
hashSalt = hashNumSalt.hexdigest()
salt = hashSalt[:4]
strPassSalt = password + salt
hashStr = SHA256.new()
hashStr.update(strPassSalt.encode("UTF-8"))
saltWithPassword = hashStr.hexdigest()
encrypted = ""
msg_encrypted_bundle = iv + ":" + salt + ":" + encrypted
msg_encrypted_bundle = msg_encrypted_bundle.replace("/" , "_")
return msg_encrypted_bundle
PHP code is working. I need to write the python code correctly because it is used in the payment system. The bank system checks the generated hash key and provides the transaction. I couldn't figure out how to edit the encrypted value. So I couldn't run the code properly. I would appreciate it if you could guide me to edit it.
I found the way to get the code working with more tries. I wanted to share in case it might be helpful. Or there may be a better way :)
def encrypt(plain_text: bytes, app_secret: str):
randNumIv = str(random.randint(100000000,900000000))
hashNumber = hashlib.sha1(randNumIv.encode("utf-8")).hexdigest()
iv = hashNumber[:16]
password = hashlib.sha1(app_secret.encode("utf-8")).hexdigest()
randNumSalt = str(random.randint(100000000,900000000))
hashSalt = hashlib.sha1(randNumSalt.encode("utf-8")).hexdigest()
salt = hashSalt[:4]
strPassSalt = password + salt
saltWithPassword = hashlib.sha256(strPassSalt.encode("utf-8")).hexdigest()
padded_text = pad(plain_text)
cipher_config = AES.new(bytes(saltWithPassword[:32], "utf-8"), AES.MODE_CBC, bytes(iv, "utf-8"))
cipher = cipher_config.encrypt(padded_text)
encrypted = base64.b64encode(cipher).decode('utf-8')
msg_encrypted_bundle = iv + ":" + salt + ":" + encrypted
msg_encrypted_bundle = msg_encrypted_bundle.replace("/" , "__")
return msg_encrypted_bundle
I was able to create a function and send the necessary information and get results.
I can't seem to do this PHP code in python:
openssl_decrypt( $acipher, "aes-256-gcm", $secret, OPENSSL_RAW_DATA, $bnonce, $tag);
This is my Python code:
from crypto.Cipher import AES
cipher = AES.new(acipher, AES.MODE_GCM, acipher)
plaintext = cipher.decrypt(secret)
# i don't even know where to put the "$bnonce, $tag", haha.
I'm quite stuck, please help.
I tried this but i got MAC check failed
secret = "b34f09fc80e7974a918b50cfda6f48ce" # after i use open-ssl on some string, this is what i received. Just for Extra Info.
content = "EZrjXFQ4o3oCVzy28q/IWYLgg9aP+VlmhQZ+wKs4C20D7k+lZMKlmi9UgZgINz6t/ucWmOzRFLo5DAZ3b6dYrMOz1lV2gJs9v9K6MDFeweREKRDupnTDKgx8AS7OaimofNK8wKxNp5QDnYBZNpY6BQ02mU5586LXGfwJJrAc3S7D85cZZofnUBpNcUztrHYOjCEtvB24p6j8W9ju3ALkAfd1Dk+UdKMVzp9sLa/qbzxPqQeZhPPS43A5b9Wa10DeKvMnMl4Z46M6j+AeudKmS46xCOM84zMbOonxIkCA+kS0BCPaDlpwOyiL31yeJqHw/dKhHuY0qVyHWgrlf3mdHizGKakeAoYsy4Wo1WjeTteSRnw713s="
# Content
bcontent = str( base64.b64decode(content) )
bcontent_total_characters = len(bcontent)
bcontent_total_characters_minus_12 = bcontent_total_characters - 12
bnonce = bcontent[ bcontent_total_characters_minus_12 : bcontent_total_characters - 12 + bcontent_total_characters ]
bcipher = bcontent[ 0 : 0 + bcontent_total_characters_minus_12 ]
# default tag
bcipher_total_characters = len(bcipher)
taglength = 16
tag = bcipher[ bcipher_total_characters - taglength : bcipher_total_characters - taglength + bcipher_total_characters ]
acipher = bcipher[ 0 : 0 + bcipher_total_characters - taglength]
cipher = AES.new(str.encode(secret), AES.MODE_GCM, str.encode(bnonce) )
decrypted = cipher.decrypt_and_verify(str.encode(content), str.encode(tag))
From php documentation i modified your function vars you can rename them if you like
openssl_decrypt( $acipher,
"aes-256-gcm",
$passphrase, //This is your secret
OPENSSL_RAW_DATA,
$iv, //This is your nonce
$tag //This is your tag
);
This is how it would look in python
#!/usr/bin/env python3
# pip install pycryptodome
import json
from base64 import b64encode,b64decode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Random import get_random_bytes
def python_encrypt(data, passphrase, iv):
"""
Encrypt using AES-256-GCM
"""
cipher = AES.new(passphrase, AES.MODE_GCM,iv)
ciphertext, tag = cipher.encrypt_and_digest(data)
json_k = [ 'nonce', 'ciphertext', 'tag' ]
json_v = [ b64encode(x).decode('utf-8') for x in [cipher.nonce, ciphertext, tag ]]
result = json.dumps(dict(zip(json_k, json_v)))
return result
def python_decrypt(data, passphrase, iv, tag):
"""
Decrypt using AES-256-GCM
"""
cipher = AES.new(passphrase, AES.MODE_GCM, iv)
decrypted = cipher.decrypt_and_verify(data, tag)
return decrypted
#Testing code out
data = b"secret"
passphrase = get_random_bytes(16)
nonce = get_random_bytes(16)
enc_json = python_encrypt(data,passphrase,nonce)
print(enc_json)
b64 = json.loads(enc_json)
json_k = [ 'nonce', 'ciphertext', 'tag' ]
jv = {k:b64decode(b64[k]) for k in json_k}
print(python_decrypt(jv['ciphertext'], passphrase, jv['nonce'], jv['tag']))
Modified from the docs
I made the following MRE to illustrate my problem. My Python code is taking a string, encrypting it with an ad-hoc created key and posting it to a Node server that is supposed to use the provided key for decryption.
Using the console output I can see that the Python output (key and encrypted message) are the same on both systems (after base64 conversion for transfer).
Also from another post on stackoverflow, I understand that the key generated by Pyhon is already base64 encoded and the decoded value needs to be split to extract the iv and key respectively.
However, I get an error saying that the iv length is invalid. I am not sure what I am doing wrong. Thanks for your hints.
Python Code fernet.py (Python 3.10.5 on Windows 11)
import requests
import base64
from cryptography.fernet import Fernet
def get_key():
key = Fernet.generate_key()
return key
def enc(input, key):
f = Fernet(key)
encrypted = f.encrypt(input.encode())
return encrypted
msg = "hello world"
key = get_key()
cypher = enc(msg, key)
print("msg: " + msg)
print("key: " + key.decode())
print("cyp: " + cypher.decode())
url='http://.../test'
headers = {'Content-type': 'application/json'}
r = requests.post(url, data={"key": key, "msg": base64.b64encode(cypher)})
Output:
msg: hello world
key: bOe3HCoveTPhHi04FvvG697O2rEkwF9vuXeZcGgbu9k=
cyp: gAAAAABi0WYLxwlMG7RGKqISZABsPlmenUmN8Ii81rJBIQdSPGzi1-IHss4atZuGp4eZIdaiR7mpBah6cQmlXxw2DzY3Ct9d7A==
Here is my node server (12.22.09 running on a Ubuntu 22.04 VM)
const express = require('express');
const url = require('url');
const crypto = require('crypto');
const app = express();
const port = 8088;
app.use(express.urlencoded({extended: true, limit:'100mb', parameterLimit:1000000}));
app.disable('x-powered-by');
function decrypt(message, fernet) {
let iv = Buffer.from(fernet, 'base64').toString('binary', 0, 16)
let key = Buffer.from(fernet, 'base64').toString('binary', 16,32);
let decoded_msg = Buffer.from(message, 'base64').toString('utf8');
let decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
let decrypted = decipher.update(decoded_msg, 'binary', 'utf8') + decipher.final('utf8');
console.log(decrypted);
return decrypted
}
app.post("/test", function(req, res) {
let fernet = req.body.key;
let message = req.body.msg;
console.log("key: " + fernet);
console.log("msg: " + Buffer.from(message, 'base64').toString('utf8'));
let dec = decrypt(message, fernet);
console.log("dec: " + dec);
res.writeHead(200, {"Content-Type": "application/json"});
res.end(JSON.stringify({"status": "OK"}));
});
app.listen(port, () => {
console.log("Listening on localhost:" + port);
});
Output:
key: bOe3HCoveTPhHi04FvvG697O2rEkwF9vuXeZcGgbu9k=
msg: gAAAAABi0WYLxwlMG7RGKqISZABsPlmenUmN8Ii81rJBIQdSPGzi1-IHss4atZuGp4eZIdaiR7mpBah6cQmlXxw2DzY3Ct9d7A==
Error: Invalid IV length
at Decipheriv.createCipherBase (internal/crypto/cipher.js:103:19)
at Decipheriv.createCipherWithIV (internal/crypto/cipher.js:121:20)
at new Decipheriv (internal/crypto/cipher.js:262:22)
at Object.createDecipheriv (crypto.js:131:10)
at decrypt (/.../app.js:55:24)
I have a question about a script I am trying to write that generates a key pair for Bitcoin adreses. I came as far as generating a random private key and generate a public key. I know (or think really much?) that my first part of code is correct. When I go to bitaddress.org and check my generated private keys for details, I always get the correct generated public key.
This is what I have now
import os
import ecdsa
import binascii
private_key = binascii.hexlify(os.urandom(32)).decode()
print("private key = " + private_key)
Private_key = bytes.fromhex(private_key)
signing_key = ecdsa.SigningKey.from_string(Private_key, curve = ecdsa.SECP256k1)
verifying_key = signing_key.get_verifying_key()
public_key = bytes.fromhex("04") + verifying_key.to_string()
print ("public key = " + public_key.hex())
The problem is that for now Im getting the 130 characters public key and I want to transform this to a bitcoin address. I don't understand how to do this. I need to do some encoding/decoding but can't wrap my head around it.
This is the explanation from the internet I found but fail to understand:
Bitcoin address explanation png
Could someone maybe help me with this
This code runs both in Python 2 and Python 3.
It prints not only the bitcoin address, but also some intermediate values.
The public key is the 130 hex char string in pubkey variable.
Please note that there are two possible and valid bitcoin addresses for each public key: the uncompressed and the compressed form. Change compress_key boolean variable to extract each one.
#!/usr/bin/env python
# https://en.bitcoin.it/wiki/Protocol_documentation#Addresses
import hashlib
import base58
# ECDSA bitcoin Public Key
pubkey = '0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6'
# See 'compressed form' at https://en.bitcoin.it/wiki/Protocol_documentation#Signatures
compress_pubkey = False
def hash160(hex_str):
sha = hashlib.sha256()
rip = hashlib.new('ripemd160')
sha.update(hex_str)
rip.update( sha.digest() )
print ( "key_hash = \t" + rip.hexdigest() )
return rip.hexdigest() # .hexdigest() is hex ASCII
if (compress_pubkey):
if (ord(bytearray.fromhex(pubkey[-2:])) % 2 == 0):
pubkey_compressed = '02'
else:
pubkey_compressed = '03'
pubkey_compressed += pubkey[2:66]
hex_str = bytearray.fromhex(pubkey_compressed)
else:
hex_str = bytearray.fromhex(pubkey)
# Obtain key:
key_hash = '00' + hash160(hex_str)
# Obtain signature:
sha = hashlib.sha256()
sha.update( bytearray.fromhex(key_hash) )
checksum = sha.digest()
sha = hashlib.sha256()
sha.update(checksum)
checksum = sha.hexdigest()[0:8]
print ( "checksum = \t" + sha.hexdigest() )
print ( "key_hash + checksum = \t" + key_hash + ' ' + checksum )
print ( "bitcoin address = \t" + base58.b58encode( bytes(bytearray.fromhex(key_hash + checksum)) ) )
Having a bit of trouble getting a AES cipher text to decrypt.
In this particular scenario, I am encrypting data on the client side with Crypto-JS and decrypting it back on a python server with PyCrypto.
encrypt.js:
var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP';
var data = 'mytext';
var masterKey = CryptoJS.SHA256(password).toString();
// Derive keys for AES and HMAC
var length = masterKey.toString().length / 2
var encryptionKey = masterKey.substr(0, length);
var hmacKey = masterKey.substr(length);
var iv = CryptoJS.lib.WordArray.random(64/8);
var encrypted = CryptoJS.AES.encrypt(
data,
encryptionKey,
{
iv: iv,
mode: CryptoJS.mode.CFB
}
);
var concat = iv + encrypted;
// Calculate HMAC using iv and cipher text
var hash = CryptoJS.HmacSHA256(concat, hmacKey);
// Put it all together
var registrationKey = iv + encrypted + hash;
// Encode in Base64
var basemessage = btoa(registrationKey);
decrypt.py:
class AESCipher:
def __init__(self, key):
key_hash = SHA256.new(key).hexdigest()
# Derive keys
encryption_key = key_hash[:len(key_hash)/2]
self.key = encryption_key
self.hmac_key = key_hash[len(key_hash)/2:]
def verify_hmac(self, input_cipher, hmac_key):
# Calculate hash using inputted key
new_hash = HMAC.new(hmac_key, digestmod=SHA256)
new_hash.update(input_cipher)
digest = new_hash.hexdigest()
# Calculate hash using derived key from local password
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.hexdigest()
return True if digest == local_digest else False
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:16]
hmac = enc[60:]
cipher_text = enc[16:60]
# Verify HMAC using concatenation of iv + cipher like in js
verified_hmac = self.verify_hmac((iv+cipher_text), self.hmac_key)
if verified_hmac:
cipher = AES.new(self.key, AES.MODE_CFB, iv)
return cipher.decrypt(cipher_text)
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'
input = 'long base64 registrationKey...'
cipher = AESCipher(password)
decrypted = cipher.decrypt(input)
I'm successful in re-calculating the HMAC but when I try and then decrypt the cipher I get something that seems encrypted with �'s in the result.
I was getting errors about input length of cipher text but when I switched to CFB mode that fixed it so I don't think it's a padding issue.
There are many problems with your code.
Client (JavaScript):
AES has a block size of 128 bit and CFB mode expects a full block for the IV. Use
var iv = CryptoJS.lib.WordArray.random(128/8);
The iv and hash variables are WordArray objects, but encrypted is not. When you force them to be converted to strings by concatenating them (+), iv and hash are Hex-encoded, but encrypted is formatted in an OpenSSL compatible format and Base64-encoded. You need to access the ciphertext property to get the encrypted WordArray:
var concat = iv + encrypted.ciphertext;
and
var registrationKey = iv + encrypted.ciphertext + hash;
registrationKey is hex-encoded. There is no need to encode it again with Base64 and bloat it even more:
var basemessage = registrationKey;
If you want to convert the hex encoded registrationKey to base64 encoding, use:
var basemessage = CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64);
concat is a hex-encoded string of the IV and ciphertext, because you forced the stringification by "adding" (+) iv and encrypted. The HmacSHA256() function takes either a WordArray object or a string. When you pass a string in, as you do, it will assume that the data is UTF-8 encoded and try to decode it as UTF-8. You need to parse the data yourself into a WordArray:
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey);
The CryptoJS.AES.encrypt() and CryptoJS.HmacSHA256() expect the key either as a WordArray object or as a string. As before, if the key is supplied as a string, a UTF-8 encoding is assumed which is not the case here. You better parse the strings into WordArrays yourself:
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length));
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length));
Server (Python):
You're not verifying anything in verify_hmac(). You hash the same data with the same key twice. What you need to do is hash the IV+ciphertext and compare the result with the hash (called tag or HMAC-tag) that you slice off the full ciphertext.
def verify_hmac(self, input_cipher, mac):
# Calculate hash using derived key from local password
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.digest()
return mac == local_digest
And later in decrypt():
verified_hmac = self.verify_hmac((iv+cipher_text), hmac)
You need to correctly slice off the MAC. The 60 that is hardcoded is a bad idea. Since you're using SHA-256 the MAC is 32 bytes long, so you do this
hmac = enc[-32:]
cipher_text = enc[16:-32]
The CFB mode is actually a set of similar modes. The actual mode is determined by the segment size. CryptoJS only supports segments of 128 bit. So you need tell pycrypto to use the same mode as in CryptoJS:
cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128)
If you want to use CFB mode with a segment size of 8 bit (default of pycrypto), you can use a modified version of CFB in CryptoJS from my project: Extension for CryptoJS
Full client code:
var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP';
var data = 'mytext';
var masterKey = CryptoJS.SHA256(password).toString();
var length = masterKey.length / 2
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length));
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length));
var iv = CryptoJS.lib.WordArray.random(128/8);
var encrypted = CryptoJS.AES.encrypt(
data,
encryptionKey,
{
iv: iv,
mode: CryptoJS.mode.CFB
}
);
var concat = iv + encrypted.ciphertext;
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey);
var registrationKey = iv + encrypted.ciphertext + hash;
console.log(CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64));
Full server code:
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
import base64
import binascii
class AESCipher:
def __init__(self, key):
key_hash = SHA256.new(key).hexdigest()
self.hmac_key = binascii.unhexlify(key_hash[len(key_hash)/2:])
self.key = binascii.unhexlify(key_hash[:len(key_hash)/2])
def verify_hmac(self, input_cipher, mac):
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.digest()
return SHA256.new(mac).digest() == SHA256.new(local_digest).digest() # more or less constant-time comparison
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:16]
hmac = enc[-32:]
cipher_text = enc[16:-32]
verified_hmac = self.verify_hmac((iv+cipher_text), hmac)
if verified_hmac:
cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128)
return cipher.decrypt(cipher_text)
else:
return 'Bad Verify'
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'
input = "btu0CCFbvdYV4B/j7hezAra6Q6u6KB8n5QcyA32JFLU8QRd+jLGW0GxMQsTqxaNaNkcU2I9r1ls4QUPUpaLPQg=="
obj = AESCipher(password)
decryption = obj.decrypt(input)
print 'Decrypted message:', decryption