Encrypt/Decrypt message using PGPy - python

I am trying out PGPy and I get an error whenever I encrypt or decrypt a message.
These are the codes that I use. Documentation and examples can be found here
Code to encrypt a message using the public key:
import pgpy
key_pub = '''BEGIN PUBLIC KEY BLOCK...END PUBLIC KEY BLOCK'''.lstrip()
message = "It worked!"
# import ASCII formatted public key
pub_key = pgpy.PGPKey()
pub_key.parse(key_pub)
# create new message
text_message = pgpy.PGPMessage.new(message)
# encrypt a message using pub key
encrypted_message = pub_key.encrypt(text_message)
print(encrypted_message)
This one gave the output but with a message
UserWarning: Selected compression algorithm not in key preferences
encrypted_message = pub_key.encrypt(text_message)
which I don't get why.
Code to decrypt the message using the priv key:
import pgpy
key_priv ='''*BEGIN PRIV KEY BLOCK...END PRIV KEY BLOCK'''.lstrip()
cipher_text = '''BEGIN PGP MESSAGE...END PGP MESSAGE'''.lstrip()
# import ASCII formatted private key
priv_key = pgpy.PGPKey()
priv_key.parse(key_priv)
message_from_blob = pgpy.PGPMessage.from_blob(cipher_text)
# decrypts a message using priv key
decrypted_message = priv_key.decrypt(cipher_text)
print(decrypted_message)
And this one does not work at all.This is the error I get.
Traceback (most recent call last):
File "C:/Users..practice.py", line 13, in <module>
decrypted_message = priv_key.decrypt(cipher_text)
File "C:\Users...venv\lib\site-packages\pgpy\decorators.py", line 126, in _action
self.check_attributes(key)
File "C:\Users...venv\lib\site-packages\pgpy\decorators.py", line 111, in check_attributes
raise PGPError("Expected: {attr:s} == {eval:s}. Got: {got:s}"
pgpy.errors.PGPError: Expected: is_unlocked == True. Got: False

Assuming that you have an RSA public and private key, encrypting a string or file can be done simply:
PUBLIC_KEY_FILE = 'path/to/keyfile/my_pub_key.asc'
pub_key, _ = pgpy.PGPKey.from_file(str(PUBLIC_KEY_FILE))
# Encrypt string
txt_msg = pgpy.PGPMessage.new("Hello PGPy World")
print('txt_msg.is_encrypted')
print(txt_msg.is_encrypted)
print('txt_msg.message')
print(txt_msg.message)
encrypted_txt_msg = pub_key.encrypt(txt_msg)
print('encrypted_txt_msg.is_encrypted')
print(encrypted_txt_msg.is_encrypted)
print('encrypted_txt_msg.message')
print(encrypted_txt_msg.message)
# Encrypt file
f_t_e = pgpy.PGPMessage.new(str(FILE_TO_ENCRYPT),file=True)
print('f_t_e.is_encrypted')
print(f_t_e.is_encrypted)
encrypted_f_t_e = pub_key.encrypt(f_t_e)
print('encrypted_f_t_e.is_encoded')
print(encrypted_f_t_e.is_encrypted)
Assuming you have the RSA private key associated with the above public key (in the recipient list) and a text file containing the passphrase, decrypting a string or file is quite straightforward:
# Load passphrase from file
with open(PASSPHRASE_FILE,'r') as ppfp:
PASSPHRASE = ppfp.readline().replace('\n','')
print(PASSPHRASE)
# Load private key
PRIVATE_KEY_FILE='path/to/keyfile/my_prv_key.gpg'
prv_key, _ = pgpy.PGPKey.from_file(str(PRIVATE_KEY_FILE))
# Unlock private key
print(prv_key.is_protected) # Should be True
with prv_key.unlock(PASSPHRASE):
print(prv_key.is_unlocked) #Should be True
# Decrypt string
decrypted_txt_msg = prv_key.decrypt(encrypted_txt_msg)
print('decrypted_txt_msg.is_encrypted')
print(decrypted_txt_msg.is_encrypted)
print('decrypted_txt_msg.message')
print(decrypted_txt_msg.message)
# Decrypt file
decrypted_f_t_e = prv_key.decrypt(encrypted_f_t_e)
print('decrypted_f_t_e.is_encrypted')
print(decrypted_f_t_e.is_encrypted)

Related

What would be an acceptable bit size for RSA encryption of an image? (PYTHON)

I'm coding a encryption module using pycryptodomex and I have run into an issue regarding the plaintext being to long for image encryption (ValueError). For my image encryption I convert the image into bytes and then encrypt the bytes. Currently I am using a key-pair with a bit size of 2048. I have tried using a key size of 4096 which takes a significantly longer amount of time to generate and use (≈100s). My question is is there a more efficient algorithms for encrypting images apart from RSA or AES (I use both however I am only getting this error whilst testing the asymmetric image encryption) and how would I encrypt images using these algorithms. I have also though of alternatives to increasing the key size these are using a if statement to prevent the user from trying to encrypt an image that is larger than the limit of encrypting an image using a 2048-bit RSA key, in this case what is the image size limit for encrypting using this kind of key? Another alternative I have though of is using compression to make the image size smaller however I would like to avoid this as this may mean loosing image quality, if nothing else works would encrypting compressed images work in the same was as encrypting standard images.
Here is the traceback for the error:
$ python -m unittest Test_TIENC_REVISED.py
EEEE....
======================================================================
ERROR: test_decrypt_failure (Test_TIENC_REVISED.TestImageEncA)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\probm\Desktop\tienc\tests\Test_TIENC_REVISED.py", line 58, in test_decrypt_failure
ciphertext = self.image_enc.encrypt(self.image_path)
File "C:\Users\probm\Desktop\tienc\tests\tienc_revised.py", line 71, in encrypt
ciphertext = cipher.encrypt(img_bytes)
File "C:\Users\probm\Desktop\tienc\venv\lib\site-packages\Cryptodome\Cipher\PKCS1_OAEP.py", line 115, in encrypt
raise ValueError("Plaintext is too long.")
ValueError: Plaintext is too long.
======================================================================
ERROR: test_encrypt_decrypt (Test_TIENC_REVISED.TestImageEncA)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\probm\Desktop\tienc\tests\Test_TIENC_REVISED.py", line 50, in test_encrypt_decrypt
ciphertext = self.image_enc.encrypt(self.image_path)
File "C:\Users\probm\Desktop\tienc\tests\tienc_revised.py", line 71, in encrypt
ciphertext = cipher.encrypt(img_bytes)
File "C:\Users\probm\Desktop\tienc\venv\lib\site-packages\Cryptodome\Cipher\PKCS1_OAEP.py", line 115, in encrypt
raise ValueError("Plaintext is too long.")
ValueError: Plaintext is too long.
Here is the code I use to test the encryption (commented out code is what I previously had):
class TestImageEncA(unittest.TestCase):
def setUp(self):
self.image_path = imgpath
self.private_key, self.public_key = tienc_revised.generate_key_a_4096() #self.private_key, self.public_key = tienc_revised.generate_key_a()
self.image_enc = tienc_revised.ImageEncA(self.public_key)
def test_encrypt_decrypt(self):
ciphertext = self.image_enc.encrypt(self.image_path)
output_path = "decrypted_image.jpg"
self.image_enc.decrypt(ciphertext, self.private_key, output_path)
with open(self.image_path, "rb") as f1, open(output_path, "rb") as f2:
self.assertEqual(f1.read(), f2.read())
def test_decrypt_failure(self):
wrong_key = RSA.generate(4096).export_key() #wrong_key = RSA.generate(2048).export_key()
ciphertext = self.image_enc.encrypt(self.image_path)
output_path = "decrypted_image.jpg"
self.assertRaises(Exception, self.image_enc.decrypt, ciphertext, wrong_key, output_path)
Here is the main code for encryption:
class ImageEncA:
""" asymmetric image encryption """
def __init__(self, public_key):
self.public_key = RSA.import_key(public_key)
def encrypt(self, image_path):
with open(image_path, "rb") as f:
img_bytes = f.read()
cipher = PKCS1_OAEP.new(self.public_key)
ciphertext = cipher.encrypt(img_bytes)
return ciphertext
def decrypt(self, ciphertext, private_key, output_path):
private_key = RSA.import_key(private_key)
cipher = PKCS1_OAEP.new(private_key)
img_bytes = cipher.decrypt(ciphertext)
with open(output_path, "wb") as f:
f.write(img_bytes)
Here is the code for generating keys:
# Asymetric key generation
def generate_key_a():
key = RSA.generate(2048)
private_key = key.export_key()
public_key = key.publickey().export_key()
return private_key, public_key
# Symetric key generation
def generate_key_s():
key = os.urandom(32)
return key
# Asymetric key generation with a larger bit size
def generate_key_a_4096():
key = RSA.generate(4096)
private_key_b = key.export_key()
public_key_b = key.publickey().export_key()
return private_key_b, public_key_b
So how would I fix this problem?

urlsafe_b64decode: return binascii.a2b_base64(s)

I'm working on python cryptography by following tutorial.
here is the code:
import os
import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
BASE_DESTINATION = os.path.dirname(os.path.abspath(__file__))
KEY_DESTINATION = os.path.join(BASE_DESTINATION, 'keys/')
def generate_key_from_password(path = KEY_DESTINATION):
passwordProvided = str(input("Enter Password: ")) # enter your password
password = passwordProvided.encode()
salt = os.urandom(16)
# salt = b'aeroplane'
kdf = PBKDF2HMAC(
algorithm= hashes.SHA256(),
length= 32,
salt= salt,
iterations= 100_000,
backend = default_backend()
)
key = base64.urlsafe_b64decode(kdf.derive(password)) # generated successed!
print("key: ", key)
generate_key_from_password()
when key is generated with base64.urlsafe_b64decode(kdf.derive(password)) I get error: binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4
I visited many answers but nothing quite really fit for my code. ie: I added '=' and '==' to password string to solve padding problem and adding padding by calculation password = str(password) + ('=' * len(password)) and but it didn't work either.
Here is the full error:
Enter Password: pass
Traceback (most recent call last):
File "gfp.py", line 33, in <module>
generate_key_from_password()
File "gfp.py", line 28, in generate_key_from_password
key = base64.urlsafe_b64decode(kdf.derive(password)) # generated successed!
File "C:\Users\user\Anaconda3\envs\IRIS-REC\lib\base64.py", line 133, in urlsafe_b64decode
return b64decode(s)
File "C:\Users\user\Anaconda3\envs\IRIS-REC\lib\base64.py", line 87, in b64decode
return binascii.a2b_base64(s)
binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4
The solution is provided by #Topaco in the comments. (Thank you!)
The problem was in the syntax.
it should be base64.urlsafe_b64encode(...) instead of base64.urlsafe_b64decode(kdf.derive(password)). I've seen the code multiple times but somehow I missed this everytime.
Thank you #topaco

ecdsa signing key format

I am trying to create a public/private key pair using python.
I have created a private key using the following method:
private_key = ''.join(['%x' % random.randrange(16) for x in range(0, 64)])
using this private key I have attempted to use a ecdsa graph to generate to corresponding public key
def privateKeyToPublicKey(s):
sk = ecdsa.SigningKey.from_string(s, curve=ecdsa.SECP256k1)
vk = sk.verifying_key
return ('\04' + sk.verifying_key.to_string())
I have not been able to create the signing key (sk) due to a formatting error where my string is in the wrong format. But I am not sure how/what format the string s should be for SigningKey to work.
I get the following error when running the script:
Traceback (most recent call last):
File "address.py", line 23, in <module>
privateKeyToPublicKey(private_key)
File "address.py", line 20, in privateKeyToPublicKey
sk = ecdsa.SigningKey.from_string(s, curve=ecdsa.SECP256k1)
File "/usr/local/lib/python3.6/dist-packages/ecdsa/keys.py", line
149, in from_string
assert len(string) == curve.baselen, (len(string), curve.baselen)
AssertionError: (64, 32)
Here's a more complete code sample for clarity about the answer. Python3.
from ecdsa import SigningKey, SECP256k1
import sha3, random, binascii
private_key = ''.join(['%x' % random.randrange(16) for x in range(0, 64)])
private_key = bytes(private_key, 'utf-8')
private_key = binascii.unhexlify(private_key)
priv = SigningKey.from_string(private_key, curve=SECP256k1)
pub = priv.get_verifying_key().to_string()
keccak = sha3.keccak_256()
keccak.update(pub)
address = keccak.hexdigest()[24:]
print(address, priv.to_string().hex())
I have realised my error, the input must be in bytes, the private key in hex format. In python2 you may use:
private_key.decode('hex')
or you may use
binascii.unhexlify
in python3

pysodium crypto_box_open throwing ValueError

I'm using libsodium with the python bindings in pysodium 0.6.6
When using crypto_box and crypto_box_open, I always get a ValueError. Here is a simple example:
import pysodium
import random
serverPK, serverSK = pysodium.crypto_box_keypair()
clientPK, clientSK = pysodium.crypto_box_keypair()
testText = "test message"
nonce1 = str(random.random())
nonce2 = str(random.random())
cipherText = pysodium.crypto_secretbox(testText,nonce1,clientPK)
message = pysodium.crypto_secretbox_open(cipherText, nonce2, clientSK)
print message
And here is the error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/.../pysodium-0.6.6/pysodium/__init__.py", line 181, in crypto_box_open
__check(sodium.crypto_box_open(msg, padded, ctypes.c_ulonglong(len(padded)), nonce, pk, sk))
File "/Users/.../pysodium-0.6.6/pysodium/__init__.py", line 70, in __check
raise ValueError
ValueError
crypto_box_keypair() creates a key pair, to be used with crypto_box().
crypto_secretbox() is not asymmetric encryption: a single key is used both to encrypt and to decrypt. If this is actually what you want, the key can be generated that way:
key = pysodium.randombytes(pysodium.crypto_secretbox_KEYBYTES)
For a given key, the nonce has to be unique for each message, and has to be pysodium.crypto_secretbox_NONCEBYTES bytes long. It can be a simple counter, or a random nonce:
nonce = pysodium.randombytes(pysodium.crypto_secretbox_NONCEBYTES)
Unlike the secret key, a nonce can be public. But it has to be the same for encrypting and for decrypting:
cipherText = pysodium.crypto_secretbox(testText, nonce, key)
message = pysodium.crypto_secretbox_open(cipherText, nonce, key)
The libsodium documentation provides examples on how to use box and secretbox, which can be easily translated to pysodium equivalents.

PyCrypto export/import of signature

created a client-server application with sockets and I am trying to transfer the signature from client to server. I convert it from tuple to string and then back to tuple. But signing stops working. How to resolve this?
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
public_key_file = open('public.pem','r')
public_key = RSA.importKey(public_key_file.read())
signature = "(90392831408741910958006452852395405116864328891950288888434929210668328849466319419951775157374761930395371626801844365799774616689823184955256615103504859356914334395152128600862146719619859327119380994333493461955529620578485576675021993313219918726432622856542420570716350341841652548574072964446809201965L,)"
signature_tuple = signature.split(",")
message = "Block_Height:1 From:c52030257a864a67ae4ef8a726282ed2b6b273fbccb474885027a857 To:2 Amount:3"
if public_key.verify(message, signature_tuple) == True:
print "Signature valid"
.
Traceback (most recent call last):
File "C:\Users\kucerjan\Desktop\test\sco\public_test.py", line 12, in <module>
if public_key.verify(message, signature_tuple) == True:
File "build\bdist.win32\egg\Crypto\PublicKey\RSA.py", line 221, in verify
return pubkey.pubkey.verify(self, M, signature)
File "build\bdist.win32\egg\Crypto\PublicKey\pubkey.py", line 126, in verify
return self._verify(M, signature)
File "build\bdist.win32\egg\Crypto\PublicKey\RSA.py", line 257, in _verify
return self.key._verify(m, s)
File "build\bdist.win32\egg\Crypto\PublicKey\_slowmath.py", line 73, in _verify
return self._encrypt(sig) == m
File "build\bdist.win32\egg\Crypto\PublicKey\_slowmath.py", line 65, in _encrypt
return pow(m, self.e, self.n)
TypeError: unsupported operand type(s) for pow(): 'str', 'long', 'long'
This signature is already converted to string using str(signature). I basically need to convert it to string and back.
Function reference: https://www.dlitz.net/software/pycrypto/api/current/Crypto.PublicKey.RSA._RSAobj-class.html#verify
Public key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFiMH7Lbd4JPFug8TaxX1DT8ad
lzzGm7CG1js0IQn2pCPPWBS+io1i0iUPmj78IOtUuoBqtEYGPgwqguYHozBuvdJy
Lcz4C2bYcjb2l8mQ4PM7iaCN4eHB+4xa+iJduogTjq8gx5m3j5mttEGUbZc2Q/AO
yde592P2iuRIrXcLuwIDAQAB
-----END PUBLIC KEY-----
The problem is in deserializing the signature tuple.
PyCrypto is expecting a tuple with an integer as the first value, you are passing it a string with a beginning paren "(" and then a string version of a number.
Instead of doing this:
signature_tuple = signature.split(",")
do this
signature_tuple = eval(signature)
That will properly parse the signature.
Now, there are security risks with using eval. So, if I were you, I'd come up with a better serialization/deserialization process.
The best way is to use PKCS1_v1_5 for real applications in combination with base64 for encoding and decoding the signature between client and server. No eval is needed.
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
import base64
message = 'To be signed'
key = RSA.importKey(open('privkey.der').read())
h = SHA.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
signature_enc = str(base64.b64encode(signature))
#print signature_enc
signature_dec = str(base64.b64decode (signature_enc))
#print sugnature_dec
key = RSA.importKey(open('pubkey.der').read())
h = SHA.new(message)
verifier = PKCS1_v1_5.new(key)
if verifier.verify(h, signature_dec):
print "The signature is authentic."
else:
print "The signature is not authentic."

Categories

Resources