I have two asymmetric key pairs client_key, A and another_key, B.
I want to send a encrypt a dict with B's public key. However, the dict must contain A's public key, like the following example:
message_dict = {
'hostname': socket.getfqdn(),
'request': 'MY_REQUEST',
'client_pub_key': client_key.publickey().exportKey(),
}
I did a json.dumps of the above and encrypted with B's public key. After I decrypt it, I do not get the same message. I noticed that it goes wrong only if I include client_pub_key in the dict.
I have been looking for answers online and this is possibly why it might be going wrong:
The message is too long to encrypt with B's public key
The client_pub_key has special characters which leads to a situation where JSON encoding is messed up
I tried the following:
Used pickle instead of JSON
Used binascii.hexlify to encode client_pub_key
Used base64.b64encode to encode client_pub_key
Used Crypto.Util.RFC1751.key_to_english to convert to some characters client_pub_key
Set B's key 2048 bytes and A's key 1024 bytes
All my above efforts failed. Hence I'm here looking for help.
My Question: How do I encrypt a message of the above dict format so I can send it over to the server?
(I'm using thrift's TTornadoStreamTransport and TBinaryProtocol, if that helps)
My test code snippet:
#!/usr/bin/env python
import base64
import binascii
import json
import pickle
import socket
from Crypto.PublicKey import RSA
from Crypto.Util.RFC1751 import key_to_english
client_key = RSA.generate(1024)
message_dict = {
'hostname': socket.getfqdn(),
'request': 'MY_REQUEST',
'client_pub_key': int(binascii.hexlify(client_key.publickey().exportKey()),16),
}
another_key = RSA.generate(2048)
print '\n\nDICT: {0}'.format(message_dict)
message = json.dumps(message_dict)
print '\n\nMESSAGE: {0}'.format(message)
encrypted = another_key.publickey().encrypt(message, 32)[0]
print '\n\nENCRYPTED: {0}'.format(encrypted)
decrypted = another_key.decrypt(encrypted)
print '\n\nMESSAGE: {0}'.format(decrypted)
result = json.loads(decrypted)
print '\n\nDICT: {0}'.format(result)
# result should be same message_dict
Your problem will be, that the message which you try to encrypt should be never larger than the n-modulo!
Because:
cipher = message ** e (mod N)
If your message is numerically larger than the RSA module (in your case 2048 BITS - not Bytes), you won't get the correct ciphertext, and decription will never work. (mathematically)
In your case this happens.
You should be careful, because I guess, that after the json.dumps() the message should not larger than (in your case of the 2048bit another_key) 2048//8 = 256 bytes long - you can test it after json.dumps() via len(message).
In your abovementioned example the length of your message will be larger than 700 bytes... and 700 > 256.
Just to show the meaning of this:
first try a message with exactly 256bytes of length:
>>> import json
>>> from Crypto.PublicKey import RSA
>>> another_key = RSA.generate(2048)
>>> message_dict = {'t':'a'*248}
>>> message = json.dumps(message_dict)
>>> len(str(message))
256
>>> enc = another_key.publickey().encrypt(message, 32)[0]
>>> dec = another_key.decrypt(enc)
You will see, in this example the length of the message is exactly 256 bytes (not bits, but 256*8 = 2048 bits) long - and it works fine!
But now let us try a length of 257 bytes (more than the length of the n modulo (2048 bits = 256 bytes):
>>> import json
>>> from Crypto.PublicKey import RSA
>>> another_key = RSA.generate(2048)
>>> message_dict = {'t':'a'*249}
>>> message = json.dumps(message_dict)
>>> len(str(message))
257
>>> enc = another_key.publickey().encrypt(message, 32)[0]
>>> dec = another_key.decrypt(enc)
In this case dec != enc - because the message is numerically larger than the n modulo.
If you will mathematically understand the reason for this, take a look in the meaning of "cipher = message ** e (mod N)" ;)
Related
For my own understanding of how verifying the signature of a JWT works I tried reimplementing the example given in appendix A.2 of RFC7515, the RFC that defines the JSON web signature (or JWS). https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.2
I'm using python 3.9 and the pycryptodome library. I've managed to reproduce the octet sequence they derive for the JWS Signing Input value and I'm pretty sure I've also successfully converted their RSA key description to the valid Crypto.PublicKey.RSA object. However, my value for the signature does not match what they have and I have no idea where I am making a mistake. If anyone could help me here, I would really appreciate it!
My code is as follows
import base64
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
header = '{"alg":"RS256"}'
body = '{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}'
enc_header = base64.urlsafe_b64encode(header.encode("utf-8"))
enc_body = base64.urlsafe_b64encode(body.encode("utf-8"))
enc = enc_header + b"." + enc_body
enc = enc[:-2] # Drop b"=="
print("JWS Signing Input value:", [int(x) for x in enc]) # Matches the IETF example
# The following were extracted from their RSA key by applying for each value:
# value -> int.from_bytes(bytes=base64.urlsafe_b64decode(value + b"=" * (-len(value) % 4)), byteorder='big')
n = 20446702916744654562596343388758805860065209639960173505037453331270270518732245089773723012043203236097095623402044690115755377345254696448759605707788965848889501746836211206270643833663949992536246985362693736387185145424787922241585721992924045675229348655595626434390043002821512765630397723028023792577935108185822753692574221566930937805031155820097146819964920270008811327036286786392793593121762425048860211859763441770446703722015857250621107855398693133264081150697423188751482418465308470313958250757758547155699749157985955379381294962058862159085915015369381046959790476428631998204940879604226680285601
e = 65537
d = 2358310989939619510179986262349936882924652023566213765118606431955566700506538911356936879137503597382515919515633242482643314423192704128296593672966061810149316320617894021822784026407461403384065351821972350784300967610143459484324068427674639688405917977442472804943075439192026107319532117557545079086537982987982522396626690057355718157403493216553255260857777965627529169195827622139772389760130571754834678679842181142252489617665030109445573978012707793010592737640499220015083392425914877847840457278246402760955883376999951199827706285383471150643561410605789710883438795588594095047409018233862167884701
p = 157377055902447438395586165028960291914931973278777532798470200156035267537359239071829408411909323208574959800537247728959718236884809685233284537349207654661530801859889389455120932077199406250387226339056140578989122526711937239401762061949364440402067108084155200696015505170135950332209194782224750221639
q = 129921752567406358990993347540064445018230073402482260994179328573323861908379211274626956543471664997237185298964648133324343327052852264060322088122401124781249085873464824282666514908127141915943024862618996371026577302203267804867959037802770797169483022132210859867700312376409633383772189122488119155159
ietf_key = RSA.construct(
rsa_components=(n, e, d, p, q)
)
signer = pkcs1_15.new(ietf_key)
h = SHA256.new(enc)
signature = signer.sign(h)
pkcs1_15.new(ietf_key).verify(h, signature)
print([int(x) for x in signature]) # Does not match the IETF example
print(base64.urlsafe_b64encode(signature))
You get the result from RFC 7515, A.2.1. Encoding if you use for body:
body = '{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}'
The difference to the value you used is a 0x0d0a20 byte sequence (\r\n<space>) after each comma. This is described in A.1.1. Encoding.
I using getting the encrypted message from a third party sever.
I am using the tools in enter link description here
to test and its correct
The Encrypted Text =
"WiI9g5qo+ztSlqHMbpiezHZ2dBkQ2gprGJZyWtwcMTWPoxzLsMmujE9xDeFK4XYMfBdZGh2naMwP3LfbPy/06mazrSs66WRM1oxhz56L2UzTKyCWCl+ld7RlN7aPwfEw2j9VN50YCkMLfQRfIAXTspKQb6o5QQw8ey0cINdtWSHClz/uXiCFqiYJfItxY1rAZkE1Qj0b0izGQFJ9/44Zfw0dJtzCXYgXTPZftPeGTdoX/HnZJpUvfqmLIAdgAyoXi5BxL5bgSs30yaB4bRxJJj7DKpVbAgZmx0ecjmiGDh7t78A16pZ2kz+OIUkuc/hxvUaVehsH1pVdqycpUJfbgy+to0AY/+BBd38GGvv8YdTCa99bSHRGaZuUglLKN/2J0pZmfrIARIdgrV2yDK+IN4hTVKf1jprtfvhvkG+eRyDfoLL9rg8+ZEtdYUdZgDdF3ftmHKTzgxI6leMWX7WFRTHjxVYFVk0yWA9xXk6s/WcG6IFeGYPVF94IcLeC2eAjaMasusF+C6qyFWi6nuyFK2Gr1utvG6kg84Hu0KKYg42MHXIR1AtQW3MWaqosb54y0GutQtnD47l84/PdJvUhuE/a7uyfCjjtyh2sRRLX3WDosyRZsqjLea9EIX6oNmQMZd1WRxM86Ggt6bVOc9KY5Z7HLpLyb0lLF4sdyzfBNJB7u7vqkBzsEss1Yq+sXD0N"
Key = "1234567812345678"
And the result
After AES Decrypted Output (Base64):
And after Decode Plain Text
THe result is :
{"upPacketSN":-1,"upDataSN":-1,"topic":"v1/up/ad","timestamp":1619672797621,"tenantId":"2000034792","serviceId":"","protocol":"lwm2m","productId":"15044315","payload":{"APPdata":"MTIzNDU2NzgsNTM0OCwwNywwOCwyNTUsMTc5LDEyOCwwMDAwLDQyNTksMSw2LDAsMCwwLDAsMCwwLDI0LDMuNzAsMy43MCwxLDEsNiwsMCwwLDAsMCwwLDAsMCwwLDAsMCwtMjU1LC0yNTUsLTI1NSwxLjY5LDEuOCw0OTk4LDYwLDE0NDAsNjA="},"messageType":"dataReport","deviceType":"","deviceId":"523ede8bb7e34dd4a1bd74028d63749e","assocAssetId":"","IMSI":"undefined","IMEI":"864162041961023"}
One of the thing is that IV must be NULL to get the correct answer.
And so if i implement in Python AES CBC using pycryptodome library in the following like:
class AES_CBC:
def add_to_16(self, value):
while len(value) % 16 != 0:
value += '\0'
return str.encode(value) # 返回bytes
#解密方法
def decrypt_oralce(self, key, text):
# 初始化加密器
# 偏移量 16个0
iv = "0000000000000000"
aes = AES.new(self.add_to_16(key), AES.MODE_CBC, self.add_to_16(iv))
#优先逆向解密base64成bytes
base64_decrypted = base64.decodebytes(text.encode(encoding='utf-8'))
#
decrypted_text = str(aes.decrypt(base64_decrypted), encoding='utf-8') # 执行解密密并转码返回str
unpad = lambda s : s[0:-ord(s[-1])]
#PADDING = '\0'
#print decrypted_text.rstrip(PADDING) #zeropadding只见诶去掉结尾\0
# print(unpad(decrypted_text))
return unpad(decrypted_text)
if __name__ == '__main__':
aes = AES_CBC()
#加密
key = "1234567812345678"
enc_msg = "WiI9g5qo+ztSlqHMbpiezHZ2dBkQ2gprGJZyWtwcMTWPoxzLsMmujE9xDeFK4XYMfBdZGh2naMwP3LfbPy/06mazrSs66WRM1oxhz56L2UzTKyCWCl+ld7RlN7aPwfEw2j9VN50YCkMLfQRfIAXTspKQb6o5QQw8ey0cINdtWSHClz/uXiCFqiYJfItxY1rAZkE1Qj0b0izGQFJ9/44Zfw0dJtzCXYgXTPZftPeGTdoX/HnZJpUvfqmLIAdgAyoXi5BxL5bgSs30yaB4bRxJJj7DKpVbAgZmx0ecjmiGDh7t78A16pZ2kz+OIUkuc/hxvUaVehsH1pVdqycpUJfbgy+to0AY/+BBd38GGvv8YdTCa99bSHRGaZuUglLKN/2J0pZmfrIARIdgrV2yDK+IN4hTVKf1jprtfvhvkG+eRyDfoLL9rg8+ZEtdYUdZgDdF3ftmHKTzgxI6leMWX7WFRTHjxVYFVk0yWA9xXk6s/WcG6IFeGYPVF94IcLeC2eAjaMasusF+C6qyFWi6nuyFK2Gr1utvG6kg84Hu0KKYg42MHXIR1AtQW3MWaqosb54y0GutQtnD47l84/PdJvUhuE/a7uyfCjjtyh2sRRLX3WDosyRZsqjLea9EIX6oNmQMZd1WRxM86Ggt6bVOc9KY5Z7HLpLyb0lLF4sdyzfBNJB7u7vqkBzsEss1Yq+sXD0N"
#解密
dec_text = aes.decrypt_oralce(key, enc_msg)
print(key)
print(dec_text)
EDITED:
i got a different result using same IV 16'0'
and
the result from the web is
{"upPacketSN":-1,"upDataSN":-1,"topic":"v1/up/ad","timestamp":1619687373640,"tenantId":"2000034792","serviceId":"","protocol":"lwm2m","productId":"15044315","payload":{"APPdata":"MTIzNDU2NzgsNTM0OCwwNywwOCwyNTUsMTc5LDEyOCwwMDAwLDQ1MDQsMSw2LDAsMCwwLDAsMCwwLDI0LDMuNzAsMy43MCwxLDEsNiwsMCwwLDAsMCwwLDAsMCwwLDAsMCwtMjU1LC0yNTUsLTI1NSwxLjY5LDEuOCw0OTk4LDYwLDE0NDAsNjA="},"messageType":"dataReport","deviceType":"","deviceId":"523ede8bb7e34dd4a1bd74028d63749e","assocAssetId":"","IMSI":"undefined","IMEI":"864162041961023"}
while the result of my code is :
KE#`QS[UDc~
1,"upDataSN":-1,"topic":"v1/up/ad","timestamp":1619687373640,"tenantId":"2000034792","serviceId":"","protocol":"lwm2m","productId":"15044315","payload":{"APPdata":"MTIzNDU2NzgsNTM0OCwwNywwOCwyNTUsMTc5LDEyOCwwMDAwLDQ1MDQsMSw2LDAsMCwwLDAsMCwwLDI0LDMuNzAsMy43MCwxLDEsNiwsMCwwLDAsMCwwLDAsMCwwLDAsMCwtMjU1LC0yNTUsLTI1NSwxLjY5LDEuOCw0OTk4LDYwLDE0NDAsNjA="},"messageType":"dataReport","deviceType":"","deviceId":"523ede8bb7e34dd4a1bd74028d63749e","assocAssetId":"","IMSI":"undefined","IMEI":"864162041961023"}
can anyone help me which part i get wrong?
Thanks
Currently the code is not using a "null-IV", which means an array filled with bytes set to zero. Instead it is using an array filled with '0' characters, which have value 0x30 in hexadecimals or 48 in decimals; distinctly not zero.
To create a null-IV please have a look here on how to indicate byte values within byte arrays in Python. The same trick is used within the add_to_16 loop, where the \0 escape is used to indicate a zero byte.
Note that padding a key or IV is very bad practice. Those need to consist of randomized bytes. Beware that working crypto code is not the same thing as secure crypto code, which should probably be your goal.
for a university exercise I want to develop a simple hotp server-client system in python. In this case the client sends a password and a one time password to the server. The server knows the secret, calculates the current hotp and compares the values it receives. So far, so good. With plaintext this works perfectly fine and the calculated values are the same I get when I use the iOS App "OTP Auth". But there is also the possibility to calculate the OTP in combination with base32. So I added a few lines to encode the plaintext to base32 but now the output in not correct.
Let's assume we're using the secret "1234" so the plaintext output would be "110366". That's working. But if I'm encoding the secret to base32 the output should be "807244" but my program calculates "896513". Anybody know why this is happening?
I've already tried to use different secrets and checked it on different apps. Always the same result.
import hmac
import hashlib
import array
import base64
counter = 0
digits = 6 #Anzahl der Zeichen
def hotp(secret, c):
global digits
counter = extendCounter(c)
hmac_sha1 = hmac.new(secret, counter, hashlib.sha1).hexdigest()
return truncate(hmac_sha1)[-digits:]
def truncate(hmac_sha1):
offset = int(hmac_sha1[-1], 16)
binary = int(hmac_sha1[(offset * 2):((offset * 2) + 8)], 16) & 0x7fffffff
return str(binary)
def extendCounter(long_num):
byte_array = array.array('B')
for i in reversed(range(0, 8)):
byte_array.insert(0, long_num & 0xff)
long_num >>= 8
return byte_array
def main():
secret = "1234"
bSecret = secret.encode("UTF-8")
bSecret = base64.b32encode(bSecret)
otp = hotp(bSecret, counter)
one_time_password = otp
I expect 807244 as the output but the output is 896513
First, it's important to point out that the result of secret.encode('UTF-8') has exactly the same type as the result of base64.b32encode(bSecret) (and for that matter base64.b64encode(bSecret)) -- they all return bytes objects. Also worth noting is that the implementation of hmac in Python has no mention of base64/base32 encoding. So the short answer is that your expected result of 807244 is only valid if the shared secret is a base64/UTF-8 encoded blob.
This quick snippet shows that really you can give any bytes you like to hotp and it will come up with some result (because hotp is called multiple times in the example, counter is changed)
# ... everything from your example above ...
secret = "1234"
secret_bytes = secret.encode("UTF-8")
secret_bytes
>>> b'1234'
b32_secret = base64.b32encode(bSecret)
b32_secret
>>> b'GEZDGNA='
b64_secret = base64.b64encode(bSecret)
b64_secret
>>> b'MTIzNA=='
hotp(secret_bytes, counter) # just a UTF-8 blob works
>>> '110366'
hotp(b32_secret, counter) # base32/UTF-8 also works
>>> '896513'
hotp(b64_secret, counter) # base64/UTF-8 works as well
>>> '806744'
If you have more detail of why you expected 807244 for a base32/UTF8 blob, I'll be happy to amend this answer.
Found the mistake:
Instead of translating the secret to base32, the secret must be a Base32 decoded value. Also instead of encoding this value, it must be decoded ("base64.b32decode(bytes(saved_secret, 'utf-8'))")
So the correct main looks like this:
def main():
secret = "V6X27L5P" #Base32 value
secret = base64.b32decode(bytes(secret, 'utf-8'))
one_time_password = hotp(secret, counter)
Why cipher3 can't decrypt cipher data?
cipher2 and cipher3 use same nonce, but cipher3 can't decrypt data
Code:
>>> from Crypto.Cipher import AES
>>> cipher = AES.new(b"M"*16, AES.MODE_EAX)
>>> cipher2 = AES.new(b"M"*16, AES.MODE_EAX, cipher.nonce)
>>> cipher3 = AES.new(b"M"*16, AES.MODE_EAX, cipher.nonce)
>>> data = cipher.encrypt(b"Hello")
>>> data2 = cipher.encrypt(b"World")
>>> cipher2.decrypt(data)
b'Hello'
>>> cipher3.decrypt(data2)
b'S\xa5\x92\xa2\x9a'
>>> cipher2.decrypt(data2)
b'World'
The problem is that cipher objects you use for decryption (cipher2, cipher3 in your case) must be presented the pieces of ciphertext in the same order they were produced (by cipher in your case).
Instead, you are passing data2 as the first piece of ciphertext to cipher3, even though it was produced second.
This is applicable to several other cipher modes, not just EAX.
Note also that EAX is an authenticated cipher mode: you should use the method decrypt_and_verify() unless you have were good reasons not to.
Given a public key exponent and modulus like the following, how can I encrypt a string and send it to a server as text?
publicKey: 10001,
modulus: 'd0eeaf178015d0418170055351711be1e4ed1dbab956603ac04a6e7a0dca1179cf33f90294782e9db4dc24a2b1d1f2717c357f32373fb3d9fd7dce91c40b6602'
I am trying to replicate the functionality provided by the javascript rsa library http://www.ohdave.com/rsa/ in python. In javascript, it looks something like this:
setMaxDigits(67); //sets a max digits for bigInt
var key = new RSAKeyPair('10001', '10001', 'd0eeaf178015d0418170055351711be1e4ed1dbab956603ac04a6e7a0dca1179cf33f90294782e9db4dc24a2b1d1f2717c357f32373fb3d9fd7dce91c40b6602');
var encrypted = encryptedString(key, 'message');
console.log(encrypted); //prints '88d58fec172269e5186592dd20446c594dbeb82c01edad41f841666500c9a530e24a282c6527ec66f4c826719f12478c6535bdc2baef86e4ff26906a26398413'
I imagine there is a way to do this with the PyCrypto library but I couldn't find any examples that use the exponent and modulus.
Edit 1:
Using the solution below, it appears to be working. Since I'm using python 2.7 I modified it to look like this:
from Crypto.PublicKey.RSA import construct
from binascii import unhexlify
from codecs import encode
e = long(10001)
n = int(encode('d0eeaf17801.....5d041817005535171', 'hex'), 16)
key = construct((n, e))
a = key.encrypt('hello', None)
print(a)
('.X?\xdc\x81\xfb\x9b(\x0b\xa1\xc6\xf7\xc0\xa3\xd7}U{Q?\xa6VR\xbdJ\xe9\xc5\x1f\x
f9i+\xb2\xf7\xcc\x8c&_\x9bD\x00\x86}V[z&3\\]_\xde\xed\xdc~\xf2\xe1\xa9^\x96\xc3\
xd5R\xc2*\xcb\xd9\x1d\x88$\x98\xb0\x07\xfaG+>G#\xf7cG\xd8\xa6\xf3y_ 4\x17\x0b\x0
3z\x0cvk7\xf7\xebPyo-\xa1\x81\xf5\x81\xec\x17\x9e\xfe3j\x98\xf2\xd5\x80\x1d\xdd\
xaf\xa4\xc8I\xeeB\xdaP\x85\xa7',)
Now I want to convert this encrypted text to a string to send via a post request. But this doesn't seem to work:
a.decode('utf-8')
With PyCrypto, you can use the Crypto.PublicKey.RSA.construct() function. You'll need to convert the modulus to an int. Here's an example (assuming big-endian):
from Crypto.PublicKey.RSA import construct
e = int('10001', 16)
n = int('d0eeaf...0b6602', 16) #snipped for brevity
pubkey = construct((n, e))
Then you can do the usual things (like encrypt) with the key:
from Crypto.Cipher import PKCS1_OAEP
cipher = PKCS1_OAEP.new(pubkey)
ciphertext = cipher.encrypt(b'abcde')
Edit: Note that your public exponent, 10001, is mostly likely hexadecimal. This would correspond to the common public exponent 65537. I've updated the above to reflect that.
I tried an alternative way using Crypto.Cipher.PKCS1_OAEP motivated by: https://cryptobook.nakov.com/asymmetric-key-ciphers/rsa-encrypt-decrypt-examples and it just worked.
PS: There seems to be something wrong with modulus given, as modulus n must be the product of two large primes, thus should not be an even number. A tiny modification of n has been applied to make the example code runnable.
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import binascii
e = int('10001', 16)
n = int('d0eeaf178015d0418170055351711be1e4ed1dbab956603ac04a6e7a0dca1179cf33f90294782e9db4dc24a2b1d1f2717c357f32373fb3d9fd7dce91c40b6601', 16)
# Construct a `RSAobj` with only ( n, e ), thus with only PublicKey
rsaKey = RSA.construct( ( n, e ) )
pubKey = rsaKey.publickey()
print(f"Public key: (n={hex(pubKey.n)}, e={hex(pubKey.e)})")
# Export if needed
pubKeyPEM = rsaKey.exportKey()
print(pubKeyPEM.decode('ascii'))
# Encrypt message using RSA-OAEP scheme
msg = b'Hello, world.'
encryptor = PKCS1_OAEP.new(pubKey)
encrypted = encryptor.encrypt(msg)
print("Encrypted:", binascii.hexlify(encrypted))