Bad padding exception after encrypting a message with RSA SHA1 OAEP MGF1 - python-3.x

I need to encrypt some message before sending it as request body in an API. I have to do this in Python and the library I used is Pycryptodome. From hereon, when I use the term API, I am referring to the API endpoint provided by the vendor and not the Pycryptodome API. The receiver(API vendor) shared the certificate (RSA 2048 bits) which I need to use to encrypt this message. This certificate is in binary format (DER) and has .cer as the extension. I need to import this using Pycryptodome and from the docs (https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html), I find that the import_key method accepts X.509 certificates in either binary or PEM encoding. The API documentation says I need to use RSA/NONE/OAEPWithSHA1AndMGF1Padding for encryption. This is my code.
def encrypt_message(message):
with open('/home/krishna/work/.certificates/random_certificate.cer', 'rb') as _file:
key_contents = _file.read()
recipient_key = RSA.import_key(key_contents)
cipher_rsa = PKCS1_OAEP.new(recipient_key)
return base64.b64encode(cipher_rsa.encrypt(message.encode())).decode()
I read the PKCS1_OAEP.py file, the default mask generation function is MGF1, default hash algorithm is SHA1. Hence, I did not specify these parameters in the new function in the above code. Also, I need to send this encrypted message as a base64 string (UTF-8). After reading the API documentation and the Pycryptodome documentation, I felt this code is sufficient for encryption. Also, this code is somewhat standard and is almost the same as the one in the Pycryptodome documentation. However, I get a decryption error and the vendor tells me this is due to "BAD PADDING EXCEPTION". This should not happen since I used OAEP for padding and the vendor confirmed they are using OAEP for padding as well. This is very hard for me to debug as I do not possess the vendor's private key.
So I tried generating my own pair of private and public keys (RSA-2048) and checked if I am able to decrypt my message. And duh, I am able to decrypt my message. Here is the code for encryption and decryption using my own keys.
def encrypt_message(message):
with open('my_pub.pem', 'rb') as _file:
pub = _file.read()
recipient_key = RSA.importKey(pub)
cipher_rsa = PKCS1_OAEP.new(recipient_key)
return base64.b64encode(cipher_rsa.encrypt(message.encode())).decode()
def decrypt_message(gibberish):
with open('my_priv.pem', 'rb') as _file:
priv = _file.read()
pvt_key = RSA.importKey(priv)
cipher_rsa = PKCS1_OAEP.new(pvt_key)
return cipher_rsa.decrypt(base64.b64decode(gibberish.encode())).decode()
So where I am going wrong? They key difference between this test and the actual API call is that my keys are generated in PEM format and have .pem as the extension. In the case of the API call, I have to use a .cer certificate in DER format. But I am assuming Pycryptodome is handling this for me (from the docs).
I have a few more questions regarding this.
What is NONE in RSA/NONE/OAEPWithSHA1AndMGF1Padding?
What is the difference between import_key() and importKey() in Pycryptodome? There are 2 methods and it does not look like importKey() has some documentation to it.
Can padding exceptions occur while using OAEP? From what I read from the internet, OAEP is the best method out there, not only in terms of adding randomness during encryption, but I also read that padding exceptions are very rare when this is used.
I really need to know what is going wrong here as I am dead clueless. Any suggestions or help will be appreciated.

Related

How do I validate the Hmac using NodeJS?

I can successfully create an Hmac via NodeJS using the following code:
(slightly altered example from : https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options)
Crypto.createHmac('sha256', Crypto.randomBytes(16))
.update('I love cupcakes')
.digest('hex');
That results in a value like the following (hex-based string Hmac signature):
fb2937ca821264812d511d68ae06a643915931375633173ba64af9425f2ffd53
How do I use that signature to verify that the data was not altered? (using NodeJS, of course).
My Assumption
I'm assuming there is a method call where you supply the data and the signature and you get a boolean that tells you if the data was altered or not -- or something similar.
Another Solution?
Oh, wait, as I was writing that I started thinking...
Do I need to store the original random bytes I generated (Crypto.randomBytes(16)) and pass them to the receiver so they can just generate the HMac again and verify that the result is the same (fb2937ca821264812d511d68ae06a643915931375633173ba64af9425f2ffd53)?
If that is true that would be odd, because the parameter for Crypto.randomBytes(16) is named secret (in the official example)*. Seems like that needs to be kept secret??
Please let me know if there is a way to verify the signature on the receiving side & how I do that.
Official Documentation : A Bit Confusing
Here's the function as it is defined in the official docs:
crypto.createHmac(algorithm, key[, options])
In the function definition, you can see the second param is named key.
However, in the example they refer to it as secret
const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
.update('I love cupcakes')
.digest('hex');
console.log(hash);
Just posting the answer so if anyone in future sees this they will be able to have the definitive answer.
As the commentor (Topaco) pointed out, the simple answer is that:
The receiver who want wants to validate the Hmac simply needs to use the same key value & data and apply it to the method and retrieve the hash value.
const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
.update('I love cupcakes')
.digest('hex');
console.log(hash);
The original Hmac-creating party must provide three things for the verifying party:
data : (could be encrypted data from AES256, for example)
key : original key passed into the createHmac() method -- note: this item is called secret in the sample code by NodeJS (above).
hash :the (clearText) hash which the original creator generated when calling the createHmac() method.
With those three things the verifying party can now call the createHmac() method and determine if the hash they get matches the hash that the original hmac-creating party generated.
Doing this validates that the Data which was sent has not been corrupted or altered.
Additional Note On Key (secret)
I've come back after thinking about the Hmac a bit more.
It is required that both parties know the key (aka secret) but it does not mean that it should be exposed to others.
This must be kept secret (as the code implies) because if a nefarious type knew the value and could alter it, then they could also alter the data and generate a new key (secret) and pass it along as if the original creator sent it along (MITM - man in the middle attack).
So, the point here is that yes, both parties have to know the key (secret) value, but it should not be shared where it might be discovered by nefarious types.
Instead, it will have to be agreed upon or based upon a secret password, etc.

Is sign output of the node.js/crypto module portable?

I'm trying to create a digital signature using the crypto.sign API of the node.js crypto module. I've confirmed that I can verify the signature by giving its output buffer to crypto.verify, but is it possible to verify this output with other software (e.g. signature libraries for other languages)?
settings:
Node.js version: v14
Signature algorithm (key) used: ed25519
const keyPair = await generateKeyPair('ed25519');
const signature = crypto.sign(null, Buffer.from('data'), keyPair.privateKey);
Public key exchange format: PEM
Signature data exchange format : Base64 encoding of crypto.sign output buffer (should be PKI's CMS or JWS standard format, but...)
What I'm wondering:
How should I handle the byte order when verifying base64 back to byte string in various languages?
What other things should I be aware of?

how to decrypt the environment variables in aws lambda using python 3

I am trying to encrypt my postgres connection string variable.
db_host = os.environ.get('db_host')
db_port = os.environ.get('db_port')
db_name =os.environ.get('db_name')
db_user =os.environ.get('db_user')
db_pass =os.environ.get('db_pass')
as you can see I have used environment variable, but I want to encrypt it.
I have figured out a way to encrypt it was also checking a way to decrypt it.
Lambda function suggested a below code.
import boto3
import os
from base64 import b64decode
ENCRYPTED = os.environ['db_host']
# Decrypt code should run once and variables stored outside of the function
# handler so that these are decrypted once per container
DECRYPTED = boto3.client('kms').decrypt(
CiphertextBlob=b64decode(ENCRYPTED),
EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
)['Plaintext'].decode('utf-8')
def lambda_handler(event, context):
# TODO handle the event here
pass
Questions:
Can I include this in the same .py file which contents my lambda function?
This shows the decryption of a single variable. I have multiple variables how do I implement for that so that all are decrypted in one go?
EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
)['Plaintext'].decode('utf-8')
What is {'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
It depends on the size of your lambda script, really. If it's a really small script with very few decrypt calls, you can have them inside your Lambda function. Or else, if you want to separate decryption calls for better readability and manageability you can either create a function or have decryption in a different .py file and import it in the lambda function.
You can not decrypt multiple ciphers using the decrypt call. You might've to call decrypt multiple times to accomplish this. Otherwise, you can pack the entire content as a comma-separated list or JSON encoded array and store the encrypted value in the environment variable. This is not a recommended way to go. Highly recommended alternative solution will be to use the SSM parameter store to encrypt and store the sensitive strings. SSM get_parameters allows to get multiple parameters in a single API call. These links might help Parameter store - SecureString Parameters - boto3 ssm get_parameters
EncryptionContext is non-secret data to support additional authentication, only applicable for symmetric encryptions. If you encrypt some data using a key-value pair of EncryptionContext, you must provide the exact EncryptionContext to decrypt the same. In your example, it simply means that you've specified EncryptionContext as {'LambdaFunctionName': '<your-function-name>'}, so in order to decrypt, you must provide the exact EcryptionContext while calling decrypt.
EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']} )['Plaintext'].decode('utf-8')
This is a boto3 kms decryption option.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kms.html#KMS.Client.decrypt
so. If you want to use this option, first add the "Encryption Context" option when encrypting.
For example, for the AWS CLI
aws kms encrypt --key-id alias/SlackKey --plaintext "hooks.slack.com/services/xxxxxxxxx" --region ap-northeast-2 --encryption-context "LambdaFunctionName=[you function name]"
As above, you can attach options when encrypting.
The key is
--encryption-context "LambdaFunctionName=[you function name]"

Python PyCryptodome Digital Signature Algorithm with DSS

in Python Pycryptodome default example for DSA-DSS
Hi guys.I asked this question but , it's not clever , I deleted on my profile and just asking from my friends account.
Problem is that .I tried to use public key encryption , signature , verifiying , so..
Till tomorrow all's going well but I encounter with DSA-DSS ECDSA. If you look at picture , I think there some issue that I target it.They makes "signer" with private key in DSS , but they don't uses it in signature. Instead it using key sign .Even at verifying level , (in picture didn't appear) they call public key from "PEM" file and trying to verify without call DSS new() again..
So if you compare my code and picture then you notice actually what I want to say...
from Crypto.PublicKey import DSA
from Crypto.Signature import DSS
from Crypto.Hash import SHA256
key = DSA.generate(2048)
publickey=key.publickey()
message = b"Hello"
hash_obj = SHA256.new(message)
signer = DSS.new(key, 'fips-186-3')
signature = signer.sign(hash_obj)
So here I trying to verify message..I did't create has object again,and I called public key from key that I showed it above.
pkey=DSS.new(publickey,'fips-186-3')
pkey.verify(hash_obj,signature)
False
So as you can see , I got "False" .I tried it on ECDSA - DSS returned samething again.
so if you got what I want to do , please help , what I want to to?
The docs for the verify method say:
Raises: ValueError – if the signature is not authentic
and it always return False if it is successful.
So rather than checking the return value, you need to check if the method raises an exception.
In general you will want something like:
try:
pkey.verify(hash_obj,signature)
valid = True
except ValueError:
valid = False
In your code the fact that it doesn’t raise an exception shows that the verification has succeeded and the signature is in fact valid.

Problems decrypting data in node.js (RSA PKCS 1.5)

I am having great difficulty implementing some encryption for a node.js project. I am using node-rsa which in turn uses the built in node crypto module.
My code appears to encrypt the data just fine and I can decrypt it. Unfortunately, the system I am sending the data to cannot. Having hit a bit of a wall I've tried decryption some data generated the other end. That also fails.
What is unusual in this case is that the data has to be encrypted using the PRIVATE key and decrypted using the PUBLIC key. As stupid as this is, I had no say in the decision to use this mechanism and it is already in wide use in a UK pubic transport ticketing system. So there is no possibility of doing anything different.
The specification [only] says:
The payload data (Appendix A) is encrypted using the TIS private key,
using 1024Bit RSA PKCS#1v1.5. (This is a freely available public
standard). This protects a payload of up to 116 bytes, or 928Bits,
creating a 128 byte or 1024Bit encrypted output. The RSA encryption
algorithm adds a header of length 12 bytes to the payload before
encryption to improve security. For decryption purposes, this header
should be removed to get the correct payload.
The format of this header is: 0x00 [10 non-zero bytes which are
padding] 0x00 [content]. This is often done automatically by
encryption/decryption software.
Here is an example public key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDriuvWAKiTKFgmjHjHYjWK4IZ6
W1UOdrntxFAGT1szZlbKJfWGeBOPSk6MnW6AeX54zEta74Dnoj4MBT+MlekVQ2zp
G3qIZpp4DR+kF6eVRlguUg9l/Fgr9ZrFgkumCKIx/ccc6rvZcNvPnOVdPCBVoMRr
3X59WlLmH4d+RAivAQIDAQAB
-----END PUBLIC KEY-----
I've derived this from the Modules / Exponent listed below... I think it is correct...?
Which is [supposedly] valid to decode this encrypted data:
1f876b22ea8af4341c4198347aabf1b1ba40649ad6918bbe26ebfcf4633786a2669ee499d796cf438a76ff0d4f6efc43f605994330e6fb88c8117eea6aad1a12c2525940f7b9946b3f6cbd674e2740070400ae6f2cf1ed6d36f610822a23d5a8a4717ecf671bf0530200f75a269951020be9f69a992d423f55eb6fefe6caa449
I get the following error:
decryptData error Error: Error during decryption (probably incorrect key). Original error: Error: error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01
I hate to say it, but I am now completely stumped. If anyone is able to decrypt that data using the supplied key and can explain how I would be very grateful.
Just to save the usual response to this sort of question that I see whenever anyone else has asked a similar question in the past. Please do not comment if all you want to say is the process is wrong. That is not helpful in any way. Just to confirm, whilst encryption with the private key is unusual, it is not impossible....It is supported by the RSA specification.
There are several ticket retailers who are encrypting their data this way and several scanning equipment manufacturers who are producing equipment that decrypts in this way. It's out there, it's working.
The only issue seems to be that there is some small mismatch between the encrypted data I am seeing from node-rsa (node.js crypto library) and the example data I have been given.
This is the original format the keys were given to me.....
Appendix D - Example Security Keys for Tickets
All example tickets are encrypted using the following fixed 1024Bit RSA key
(Never use these keys in production, you must generate your own key pair for this purpose as this private key cannot be considered secure and is for testing only)
Modulus = 16540368155253773454125654483794538435594869143015457768150677916704644349481001621163 93533906833356026635527060528398017212524170789714748325265707476885395169247950736258 76338137535298120673942399072260833758038152595105311260002268650951968659366409471762 707986435753824060971148933772149012757770173722369
= HEX [00eb8aeb d600a893 2858268c 78c76235 8ae0867a 5b550e76 b9edc450 064f5b33 6656ca25 f5867813 8f4a4e8c 9d6e8079 7e78cc4b 5aef80e7 a23e0c05 3f8c95e9 15436ce9 1b7a8866 9a780d1f a417a795 46582e52 0f65fc58 2bf59ac5 824ba608 a231fdc7 1ceabbd9 70dbcf9c e55d3c20 55a0c46b dd7e7d5a 52e61f87 7e4408af 01]
Public Exponent = 65537
= HEX [010001]
Private Exponent = 23262061627321059848127951748956201956280865601289878122136319690902346303487555417896 45421978315004119428666694674801184773156427997741861134011936129888508204465790901818 10860126020371138225614906842567831455763314175530505369901364910691824152015094858350 72189773434923487177372245370361799421231555208773
= HEX [21205394 b0590501 3a8c895a ff2797c2 255ba45f adf1afce ec5a9caa 96848c11 0b89b896 f44774f0 c5119103 1f246071 e209515b c3ad4c66 6bf582d3 72312f2b 7250fe61 f6abed7f e219c08d c3985ae1 3f6b6db2 0e3c040b df7a817d 14a5a6f1 20d94047 08512132 aca00baa 29805440 4ad5dec2 1bd544bb 8938c74b 2904e645]
Just to confirm... I was given incorrect data.

Resources