Why the hex value is different in python and Javascript (Node Js) - python-3.x

I have been trying to encrypt something in Node JS and decrypt it in Python.
When I give the key(Secret key, base64 decoded) to Fernet.js, it forms a hex string which is equal to:
f790b0a226bc96a92de49b5e9c05e1ee
But when I give the same key in Python and try to convert into hex, the value is:
730ff4c7af3d46923e8ed451ee813c87f790b0a226bc96a92de49b5e9c05e1ee
Why there is a difference?
code sample for NodeJS:
let s = 'cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4='
new Buffer(s)).toString('hex')
Python:
be = base64.urlsafe_b64decode('cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=')
be.hex()

import base64 , binascii
key = "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4="
key = base64.urlsafe_b64decode(key)
# 32 bytes
f = binascii.hexlify(key)
# first 16
SigningKey = key[:16]
# next 16
EncKey = key[16:]
print (binascii.hexlify(SigningKey)) # 730ff4c7af3d46923e8ed451ee813c87
print (binascii.hexlify(EncKey)) # f790b0a226bc96a92de49b5e9c05e1ee

Related

b64decode python vs Buffer.from(ePayload, 'base64') node JS

Encryption is done is python & decryption to be done in Node js but facing following issues in JS
I guess some characters are getting escape from string within JS like 'backslash', '_', ''
Expected - Python utf-8 string should match with Node JS String
Node js
var a = 'b22KTGxtQmtRei9CTEtUKy0OY1qefbey0brGbNYaskVbrdclYyXFlkqSnolziVDMEguUB5Xx7+9vix8UpwUn8uAbQvmW/uVRM7gRAO063i4tpPD2Ao3wrgapLQQBYnUo+aB2uS5t3a4jzldKq8OUVsY9QWXRJ28vTvJuOnyR6+bpN9yDaiMHP0rdI510PRetIw=='
var lenSize = Buffer.from(a, 'base64')
console.log(lenSize.toString().length)
Buffer size is 145 but toString length is 139
VS
Python code
import base64
a = 'b22KTGxtQmtRei9CTEtUKy0OY1qefbey0brGbNYaskVbrdclYyXFlkqSnolziVDMEguUB5Xx7+9vix8UpwUn8uAbQvmW/uVRM7gRAO063i4tpPD2Ao3wrgapLQQBYnUo+aB2uS5t3a4jzldKq8OUVsY9QWXRJ28vTvJuOnyR6+bpN9yDaiMHP0rdI510PRetIw=='
lenSize = base64.b64decode(a)
print(len(lenSize))
Length is 145
Thanks in-advance...

Issue producing a valid WIF key with Python 3.6 (Pythonista - iPadOS)

I am having some issues with some code. I have set about a project for creating Bitcoin wallets in an attempt to turn a hobby into a learning experience, whereby I can understand both Python and the Bitcoin protocol in more detail. I have posted here rather than in the Bitcoin site as the question is related to Python programming.
Below I have some code which I have created to turn a private key into a WIF key. I have written this out for clarity rather than the most optimal method of coding, so that I can see all the steps clearly and work on issues. This code was previously a series lines which I have now progressed into a class with functions.
I am following the example from this page: https://en.bitcoin.it/wiki/Wallet_import_format
Here is my current code:
import hashlib
import codecs
class wif():
def private_to_wif(private_key):
extended_key = wif.create_extended(private_key)
address = wif.create_wif_address(extended_key)
return address
def create_extended(private_key):
private_key1 = bytes.fromhex(private_key)
private_key2 = codecs.encode(private_key1, 'hex')
mainnet = b'80'
#testnet = b'ef'
#compressed = b'01'
extended_key = mainnet + private_key2
return extended_key
def create_wif_address(extended_key):
first_hash = hashlib.sha256(extended_key)
first_digest = first_hash.digest()
second_hash = hashlib.sha256(first_digest)
second_digest = second_hash.digest()
second_digest_hex = codecs.encode(second_digest, 'hex')
checksum = second_digest_hex[:8]
extended_key_chksm = (extended_key + checksum).decode('utf-8')
wif_address = base58(extended_key_chksm)
return wif_address
def base58(extended_key_chksm):
alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
b58_string = ''
leading_zeros = len(extended_key_chksm) - len(extended_key_chksm.lstrip('0'))
address_int = int(extended_key_chksm, 16)
while address_int > 0:
digit = address_int % 58
digit_char = alphabet[digit]
b58_string = digit_char + b58_string
address_int //= 58
ones = leading_zeros // 2
for one in range(ones):
b58_string = '1' + b58_string
return b58_string
I then use a few lines of code to get this working, using the example private key from the above guide, as follows:
key = ‘0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D‘
address = wif.private_to_wif(key)
Print(address)
I should be getting the output: 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
Instead I’m getting:
5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbWs6eYX
It’s only the last 6 characters that differ!
Any suggestions and help would be greatly appreciated.
Thank you in advance.
Connor
I have managed to find the solution by adding a missing encoding step.
I am posting for all those who run into a similar issue and can see the steps that brought resolution.
def create_wif_address(extended_key):
extended_key_dec = codecs.decode(extended_key, 'hex')
first_hash = hashlib.sha256(extended_key_dec)
first_digest = first_hash.digest()
second_hash = hashlib.sha256(first_digest)
second_digest = second_hash.digest()
second_digest_hex = codecs.encode(second_digest, 'hex')
checksum = second_digest_hex[:8]
extended_key_chksm = (extended_key + checksum).decode('utf-8')
wif_address = base58(extended_key_chksm)
return wif_address
So above I added in a step to the function, at the beginning, to decode the passed in variable from a hexadecimal byte string to raw bytes, which is what the hashing algorithms require it seems, and this produced the result I was hoping to achieve.
Connor

Implementing RSA/pkcs1_padding in Python 3.X

I'm trying to move my code from Python 2.7 to Python 3.5
Below is the current implementation in Python 2.7 which uses M2Crypto
import M2Crypto
import hashlib
from binascii import hexlify
# Generates the signature of payload
def getSign(payload_xml):
# SHA-1 digest of the payload
dig = myDigest(payload_xml)
# Loading the privateKey PEM file
private_key = M2Crypto.RSA.load_key('privatekey')
# Generating base 16 and encoding
signature = hexlify(private_key.private_encrypt(dig, M2Crypto.RSA.pkcs1_padding))
return signature
# To generate sha-1 digest of payload
def myDigest(payload):
# This will give base 16 of SHA-1 digest
digest_1 = hashlib.sha1(payload).hexdigest()
return digest_1
sign = getSign(<mypayload_xml>)
And this is the new implementation in Python 3.5 using pycryptodome
from Crypto.PublicKey import RSA
import hashlib
from Crypto.Cipher import PKCS1_v1_5
from binascii import hexlify
def myDigest(payload):
# This will give base 16 of SHA-1 digest
digest_1 = hashlib.sha1(payload.encode('utf-8')).hexdigest()
return digest_1
def getSign(payload_xml):
# SHA-1 digest of the payload
dig = myDigest(payload_xml)
with open('privatekey', 'r') as pvt_key:
miPvt = pvt_key.read()
rsa_key_obj = RSA.importKey(miPvt)
cipher = PKCS1_v1_5.new(rsa_key_obj)
cipher_text = cipher.encrypt(dig.encode())
base_16_new = hexlify(cipher_text)
return base_16_new
new_sign = getSign(<mypayload_xml>)
However, for same payload, signatures are different. Can someone help
with the proper solution?
As already mentioned in my comment, encrypt and decrypt of PyCryptodome can only be used to encrypt with the public key and decrypt with the private key. PyCryptodome has no 1:1-counterpart to private_encrypt or public_decrypt of M2Crypto, which allows the encryption with the private key and the decryption with the public key. Instead PyCryptodome uses sign and verify, which however work differently in detail, so that private_encrypt and sign don't generate the same signature (for the same key and message):
sign implements RSASSA-PKCS1-V1_5 padding described in RFC 8017, chapter 8.2. The hash value H of the message is padded as follows:
0x00 || 0x01 || PS || 0x00 || ID || H
ID identifies the digest and is for SHA1 (see here for other digests):
(0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14
PS are 0xFF fill bytes, so that the padded message has the length of the modulus.
The padding of private_encrypt differs from RSASSA-PKCS1-V1_5 padding in such a way that ID is not added automatically. So that sign and private_encrypt generate the same signature, ID must be added manually in the context of private_encrypt, e.g:
import M2Crypto
import hashlib
from binascii import hexlify, unhexlify
key = """-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----"""
def getSign(payload_xml):
dig = myDigest(payload_xml)
private_key = M2Crypto.RSA.load_key_string(key)
signature = hexlify(private_key.private_encrypt(unhexlify('3021300906052b0e03021a05000414' + dig.hexdigest()), M2Crypto.RSA.pkcs1_padding))
return signature
def myDigest(payload_xml):
digest_1 = hashlib.sha1(payload_xml)
return digest_1
sign = getSign(b"Hello world")
print("M2Crypto: " + sign)
As a site note, there is a bug in the original code: private_encrypt expects the data in binary format and not as hexadecimal string.
The corresponding PyCryptodome code could be e.g.:
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA1
from Crypto.PublicKey import RSA
key = """-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----"""
def getSign(payload_xml):
dig = myDigest(payload_xml)
private_key = RSA.import_key(key)
signature = pkcs1_15.new(private_key).sign(dig)
return signature
def myDigest(payload_xml):
digest_1 = SHA1.new(payload_xml)
return digest_1
sign = getSign(b'Hello world')
print("PyCryptodome: " + sign.hex())
With the following test key (for simplicity a 512 bit key):
key = """-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----"""
both codes provide for the following message:
payload_xml = b'The quick brown fox jumps over the lazy dog'
the following signature:
8324a560e6934fa1d1421b9ae37641c3b50a5c3872beecea808fbfed94151747aad69d5e083a23aa0b134d9e8c65e3a9201bb22ec28f459e605692e53965ad3b
Conclusion: It is possible to modify the M2Crypto code so that the result corresponds to the PyCryptodome code by simply adding ID. The other way around, however, it doesn't seem to be possible, because the PyCryptodome implementation adds ID automatically and this apparently can't be prevented.
In the second snippet, you are encrypting the SHA-1 digest using the PKCS#1 1.5 algorithm (Crypto.Cipher.PKCS1_v1_5 module). That is not a signature.
Instead, you should use the Crypto.Signature.pkcs1_15 module of pycryptodome. For instance, see the example taken from here:
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
message = 'To be signed'
key = RSA.import_key(open('private_key.der').read())
h = SHA256.new(message)
signature = pkcs1_15.new(key).sign(h)

PyCrypto AES-CTR mode get different output

I use PyCrypto to encrypt my data, but why theirs output is different?
from Crypto.Cipher import AES
from Crypto.Util import Counter
data = b'\x02\x01\xf2\xca\x04\x03\x02P\x02\x02\x01\x80\xd0\x0f\x80\xd0\x0f'
key = b'random 16 string'
nonce = b'string 16 rand\x00\x01'
cipher = AES.new(key, AES.MODE_CTR, counter=lambda: nonce)
data_encrypted = cipher.encrypt(data)
print(data_encrypted)
# output is: b'_M\xed(\t4\xc4\x94\x80\x83K\x94qL\x15+R'
counter = Counter.new(2 * 8, prefix=b'string 16 rand', initial_value=1)
cipher = AES.new(key, AES.MODE_CTR, counter=counter)
data_encrypted = cipher.encrypt(data)
print(data_encrypted)
# output is: b'_M\xed(\t4\xc4\x94\x80\x83K\x94qL\x15+\xce'
Both methods will cause a memory leak. How to use pycryptodome to achieve the first effect

Calculating sha1 in Node.js returns different result than in PHP

I'm calculating SHA1 using the following PHP code:
$hash = base64_encode(sha1($password.$key, true).$key);
But when I do this in Node.js, it does not give me the same result:
var hash = crypto.createHash('sha1').update(password + key).digest('base64');
Why are the results different?
In your PHP code, you're appending the key to the sha1 before passing it to base64:
sha1($password.$key, true).$key
In order to replicate that in Node.js, you'll need to do the same:
var hash = crypto.createHash('sha1').update(password + key).digest('hex');
var result = new Buffer(hash + key).toString('base64');
Edit: after looking at the PHP docs on sha1, it looks like the second parameter being passed to sha1 is going to return non-hex data:
If the optional raw_output is set to TRUE, then the sha1 digest is instead returned in raw binary format with a length of 20, otherwise the returned value is a 40-character hexadecimal number.
So in order for the two snippets to function the same, you'd also need to modify the PHP to not pass that parameter:
$hash = base64_encode(sha1($password.$key).$key);
You need to append key in the nodejs:
// php
$hash = base64_encode(sha1($password.$key, true).$key);
// see this ^^^^
// node
var hash = crypto.createHash('sha1').update(password + key).digest('base64') + key;
// add this ^^^^^^

Resources