First things first,
SECURITY DISCLAIMER:
I'm fully aware that if I use private-encryption -> public-decryption the "encrypted" message is is readable for anyone who has access to the so called "public key".
In a normal use case of rsa this would be fatal!But in my case the "public key" is meant to be private too.
I'm trying to establish an encrypted connection where the full control stays on one party.
One of the partners is untrusted and if he would got compromised I want to be sure no one else can use the interface (to prevent privilege escalation).
As you can see in this use case encryption itself is in fact not the main reason of asymmetric "encrytion".
You actually get an encrypted data stream which can't be manipulated from a third party even if the key is leaked.
My actual communication should run between a node.js and a python environment.
The URSA node lib already has a feature for the private encryption.
Sadly pyCrypto doesn't support that for reasons mentioned above earlier.
I already tried to cheese the lib and used the private exponent to instantiate a public key object but nodejs can't decrypt the result(not a big surprise).
Does anyone know how to tackle this problem?
Is there another way to get python to do actual private key encryption?
Maybe there is another python lib capable of doing so i couldn't find?
Or maybe its possible to get the nodejs lib to decrypt my cheesie private key encryption?
I have found a working solution for me.
I simply hacked an extra method in the signing object of pyCrypto which does exactly what i want.
This works with python 2.7.9 and 3.4 as far as I tested it.
In the file "[...]/Crypto/Signature/PKCS1_v1_5.py" is the class "PKCS115_SigScheme"
I simply added following method:
def private_encrypt(self, msg):
modBits = Crypto.Util.number.size(self._key.n)
k = ceil_div(modBits,8) # Convert from bits to bytes
PS = bchr(0xFF) * (k - len(msg) - 3)
m = self._key.decrypt(b("\x00\x01") + PS + bchr(0x00) + msg)
S = bchr(0x00)*(k-len(m)) + m
return S
It's just a manipulated copy of the "sign" function.
Usage looks like:
from Crypto.Signature import PKCS1_v1_5
from Crypto.PublicKey import RSA
plain_message="Hi, I'm going to be encrypted with a private key."
key = RSA.generate(4096)
signer = PKCS1_v1_5.new(key)
bin_enc_msg = signer.private_encrypt(plain_message.encode())
The decryption after the base64 encoded transfer in nodejs with URSA publicDecrypt works fine.
It should be clear from the imports but I want to mention this nonetheless:
This actually encrypts with PKCS1.5 padded rsa.
Another solution would be to combine symmetric encryption with asymmetric signing to be able to verify it but that would require additional effort and complexity I don't want or need.
If your goal is to connect a python environment and Node.js with encryption and don't require the use of third party verification, are you required to use RSA Public key encryption? It really sounds like what you're trying to accomplish is Symmetric-key Encryption and you could use AES. The pyCrypto Library already supports this, and some googling provided this library for Node.js on git hub.
Related
I'm implementing an RSA encryption scheme where the MySQL database data is encrypted using a public key, and only the private key can be used to decrypt it.
I've managed to do encryption and decryption using the PHP openssl library, and even added a passphrase to protect the private key.
// $sRawText is the text string to encrypt.
// $sPublicKey is the public key stored on the server.
openssl_public_encrypt($sRawText, $sResult, $sPublicKey, OPENSSL_PKCS1_OAEP_PADDING);
// $sResult is the encrypted result which can then be stored in the database.
It works great for all the purposes I need to be publicly available, which is to add new entries if a user signs up or, in the future, verify details if they log in. This is sensitive data like contact information, which I only ever use very occasionally. Data that's verify only like passwords can be hashed still, and anything that needs to be read doesn't get encrypted.
Here's how I'm presently decrypting with PHP:
// $sPrivateKey is the matching private key.
// $sPassPhrase is the pass phrase (required to decrypt the result).
// $sRawBytes is the encrypted data from the database to decrypt.
$kRsaKey = openssl_pkey_get_private($sPrivateKey, $sPassPhrase);
openssl_private_decrypt($sRawBytes, $sResult, $kRsaKey, OPENSSL_PKCS1_OAEP_PADDING);
// $sResult will be the decrypted data.
The problem with this decryption approach is that it's all taking place live on the server. Obviously, storing the private key on that server would defeat most of the security benefits, as anyone with enough access to the server could pretty easily take it. The best I can come up with using PHP on the server is to pass the private key and/or pass phrase at each time of decryption. However, that still has the key being live there, and also opens up a new attack avenue for the key and pass phrase to be intercepted in those requests.
What I'm trying to do is perform all the decryption offline. So an offline software will be provided the encrypted data in a text file, which it translates into a list of instructions based on the data and intended operation. The private key never leaves that isolated environment and the pass phrase is never stored between sessions.
I'm running into all kinds of hurdles setting up an offline PHP environment, so I thought it might be easier to try to do the decryption in Python. Everything should be standard, or so I thought.
I'm currently trying to use the Python RSA library here:
https://stuvel.eu/python-rsa-doc/reference.html#exceptions
The thing I can't figure out is how to decrypt using the passphrase-protected private key. Would anyone be able to post a really simple example, or is it fundamentally not compatible without major modifications to the Python RSA library? What's the easiest approach to be able to do this decryption offline?
Okay I'll post the answer now that I figured out.
(1) We need to get the cryptography library in Python.
On Windows, enter into the command prompt (not Python interpreter):
py -m pip install cryptography
If the above doesn't work, try replacing 'py' with 'python'.
(2) The private key needs to be in a text file "privatekey.txt".
(3) Use this code for decryption:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
#privatekey.txt must contain the private key.
#passphrase_bytes must be the passphrase, and it needs to be in bytes (ie b'My Pass Phrase')
with open("privatekey.txt", "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=passphrase_bytes,
)
#rawtext_bytes must have the bytes of the ciphertext to decrypt
plaintext = private_key.decrypt(
rawtext_bytes,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
#plaintext will now contain the result
I'm not finding an API, but because it seems to delegate to OpenSSL, perhaps I'm not understanding how this works.
What I'm looking for is the equivalent of the first answer to this [1] but from within Python, using an RSA keypair that has just been generated and, for security reasons, has never been written to disk.
[1] How do I extract the private key components $N$ and $D$ from a private RSA key?
You want to call private_numbers on your RSAPrivateKey object. This will return an RSAPrivateNumbers object that has integer forms of the key components.
I'm writing this question + answer because I struggled a lot (maybe because of a lack of experience), got lost in many different ways of encrypting/decrypting things with node or python.
I thought maybe my case could help people in the future.
What I needed to do:
Get data from a form, encrypt them using Crypto (node-js)
Pass the encrypted data in Python and decrypt it using PyCrypto.
I chose to use the AES encryption.
Here is how I started (I'm not gonna go through everything I tried):
I followed the example at the end of this page
Which gave in my case:
(this might be a very bad mix between javascript and coffeescript)
crypto = require "crypto"
[...]
key = "mykeywhatever"
cipher = crypto.createCipher('aes192', key)
cipher.update('string i want to encode', 'binary', 'hex')
encoded_string = cipher.final('hex')
[...]
This worked pretty fine to encode my string.
Then I wrote my python script to decrypt this string, using the readme on PyCrypto's github's page:
from Crypto.Cipher import AES
[...]
my_string = data_coming_from_rabbitmq
obj = AES.new('mykeywhatever', AES.MODE_CBC)
obj.decrypt(ciphertext)
[...]
This obviously didn't work: in the readme there is an IV but since I didn't gave one in the node script, why would I give one in the python one?
After more googling, I learnt that node's Crypto uses OpenSSL, while PyCrypto apparently doesn't. So I looked into that and found those pages:
How can I decrypt something with PyCrypto that was encrypted using OpenSSL?
Is AES the same in libraries PyCrypto & Node.JS Crypto
and a lot more...
So things got complicated, no one is doing the same thing to decrypt data, I got lost, and asked for help.
The answer is what my coworker and I came up with (well, mostly my corworker).
So we started from the "How can i decrypt... OpenSSL" 's answer.
We needed to modify the encryption script which gave:
crypto = require "crypto"
[...]
var iv = new Buffer('asdfasdfasdfasdf')
var key = new Buffer('asdfasdfasdfasdfasdfasdfasdfasdf')
var cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
cipher.update(new Buffer("mystring"));
var enc = cipher.final('base64');
[...]
iv needs to be 16bytes long, key is 32bytes. And we changed createCipher to createCipheriv.
Back to the python decryption script:
Process was simply reading PyCrypto's documentation, and compare with the code we started from.
Then we decided to just stick to the API, and start from scratch. And it gave:
from base64 import b64decode
from Crypto.Cipher import AES
[...]
iv = 'asdfasdfasdfasdf'
key = 'asdfasdfasdfasdfasdfasdfasdfasdf'
encoded = b64decode('my_encrypted_string')
dec = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
value = dec.decrypt(encoded)
And it was as simple as that...
Hope it'll help some of you!
Update:
As Perseids wrote in the comments of his answer, the IV has to be random and different for every message
The system you are building is probably insecure
Except for storage you basically never want to just encrypt your data, but also authenticate it. Authentication in this context means that a valid message can only be generated by someone who knows the key. A widely used authentication scheme is HMAC.
If you do not authenticate your messages anyone can feed data into your service. An attacker might not be able to fully control the outcome after decryption but he/she might still be very dangerous. For example, if you use CBC (which you do) and the most common paddings schemes (AES is a block cipher and can only encrypt 128bit Blocks of data) and an attacker can differentiate between a padding error and any other error then all your messages can be decrypted by an attacker. This is called a padding oracle attack and is far too common.
To protect from this class of attacks you can use an authenticated encryption scheme, for example the GCM blockcipher mode.
Also you have to protect against replay attacks. Consider a banking application and the data you are transmitting is a bank transfer order. Barring any TAN an attacker might record a previous transaction and replay this transaction to your service again and again thus transferring a multiple of the money the customer originally wanted to.
Is the form you are getting the data from transmitted over HTTPS? If not: Can the key be eavesdropped by an attacker? How does a user know he got the form from you and not anybody else (SSL/TLS is as much about authentication as it is about confidentiality).
Probably I have forgotten some other attack vectors simple CBC-encryption offers.
Alternatives
Probably the easiest way to protect against these attacks is to transmit the form data over HTTPS. SSL/TLS was designed to prevent all of the above attacks and the client and server side implementations had a long time to mature.
I came across an nice encryption module ezPyCrypto for python's PyCrypto module. In the description it says that the user can specify between RSA and ElGamal encryption, but I can't figure out where I'm supposed to specify this in the code (API here).
Has anyone else managed this or have any experience with ezPyCytpo? Any help would be hugely appreciated.
Here's the description I was talking about:
Features:
ezPyCrypto lets you:
Generate, export and import public and private keys
Encrypt and decrypt strings with ease
Optionally create encrypted data as email-friendly text
Sign and verify strings (incl. documents)
Protect your private key with a passphrase
Create 'streams', for sending data through secured sockets
Choose any public key size you like (2048-bit recommended)
Choose between RSA and ElGamal for public key, and IDEA, DES3, Blowfish, ARC4, IDEA for session key
Rest in the comfort of security, with 256-bit session keys and defences against common RSA and ElGamal attacks, which will painfully frustrate anyone seeking to violate your privacy.
I am working on a project to implement digital signatures of outgoing messages and decided to use M2Crypto for that.
I have a certificate (in DER format) from which I extract the keys to sign the message. For some reason I keep getting an ugly segmentation fault error when I call the "sign_update" method.
Given the previous examples I have read here, I am clearly missing something.
Here is the example I am working on:
from M2Crypto.X509 import *
cert = load_cert( 'certificate.cer', format=1 )
Pub_key = cert.get_pubkey()
Pub_key.reset_context(md='sha1')
Pub_key.sign_init()
Pub_key.sign_update( "This should be good." )
print Pub_key.sign_final()
Thanks in advance for the help,
Pablo
One obvious thing jumps at me: you say your certificate is in DER format, but you are passing format=0 to load_cert() which means PEM. See X509 module variables. Maybe not what is causing your issue, though (I would expect you'd get an exception if you mix the cert type).
Update After some more thought, I think you are trying to do the wrong thing here, which is why it is crashing (although it of course should not crash but raise an exception). You can't sign a message using the public key from a certificate. That would be like doing digital forgery.
Think of it this way. You receive my certificate, which contains my public key. You can use the public key to encrypt a message to me. Only I will be able to decrypt using my private key. You can sign the message using your private key, and I can use your public key to verify your signature.
There is no private key file, thats why it crashes and I can not sign it.