I'm trying to replicate the following python code in ruby. The python code works fine, the ruby code fails due to "padding check failed". I can encrypt a string and decrypt it with the private key in Ruby, but the encrypted data I need to work with is being retrieved from elsewhere. I'm unsure of the library and even language used to encrypt it, but the Python code works.
Working Python code:
def decrypt_secret(encrypted_base64, private_key):
key = RSA.importKey(open(private_key, "r").read())
pkey = PKCS1_OAEP.new(key, hashAlgo=SHA256)
encrypted_blob = base64.urlsafe_b64decode(encrypted_base64)
decrypted_string = pkey.decrypt(encrypted_blob)
return decrypted_string
Ruby Code fails "padding check failed"
def decrypt_secret(encrypted_base64, private_key)
key = File.read(private_key)
pkey = OpenSSL::PKey::RSA.new(key)
encrypted_blob = Base64.urlsafe_decode64(encrypted_base64)
decrypted_string = pkey.private_decrypt(encrypted_blob, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
return decrypted_string
end
I found the needed functions contained in the JOSE gem to support SHA256 OAEP. The following code does the job:
require 'jose'
def decrypt_secret(encrypted_base64, private_key)
key = File.read(private_key)
pkey = OpenSSL::PKey::RSA.new(key)
encrypted_blob = Base64.urlsafe_decode64(encrypted_base64)
decrypted_string = JOSE::JWA::PKCS1::rsaes_oaep_decrypt(OpenSSL::Digest::SHA256, encrypted_blob, pkey)
return decrypted_string
end
It doesn't look like Ruby's OpenSSL wrapper exposes the option to change the hash function for OAEP. You would need to change the Python code to use SHA-1 (default):
pkey = PKCS1_OAEP.new(key)
Related
I have a project written in python. I use cryptography library to encrypt and decrypt data.
I do it how is shown in their tutorial.
Here is my python code:
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
password = b"my password"
salt = os.urandom(16)
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=default_backend())
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
data = b"my data..."
token = f.encrypt(data)
Then for decryption I can just use:
f.decrypt(token)
Everything works perfectly in python but now I need to do the same thing in kotlin. I found out about fernet java-8 library but I don't know how to use it in the same way.
The problem is that I have two tools: one is written in python and another I want to write in kotlin. Both tools are meant to do the same thing - the python one is for desktop and the kotlin one is gonna be an android app. So it is really important for their encryption to be the same, so that files encrypted in python (desktop tool) can be decrypted in kotlin (android app) and vice versa.
But I don't know how to write analogous kotlin code.
You see there is a function (or class) called PBKDF2HMAC and there is also base64.urlsafe_b64encode and others. And I don't know what are analogous functions in kotlin or fernet java-8.
So how should I do it? Assuming that in kotlin I have to use password and salt I used in python.
Thanks!
In Java/Kotlin, using fernet-java8, the token generated with the Python code could be decrypted as follows:
import java.security.SecureRandom
import java.util.Base64
import javax.crypto.spec.PBEKeySpec
import javax.crypto.SecretKeyFactory
import com.macasaet.fernet.Key
import com.macasaet.fernet.Token
import com.macasaet.fernet.StringValidator
import com.macasaet.fernet.Validator
import java.time.Duration
import java.time.temporal.TemporalAmount
...
// Data from encryption
val salt = Base64.getUrlDecoder().decode("2Yb8EwpYkMlycHxoKcmHuA==")
val token = Token.fromString("gAAAAABfoAmp7C7IWVgA5urICEIspm_MPAGZ-SyGnPEVUBBNerWQ-K6mpSoYTwRkUt3FobyAFHbYfhNtiGMe_96yyLvUoeLIIg==");
// Derive Fernet key
val key = deriveKey("my password", salt)
val fernetKey = Key(key)
// Decrypt
val validator: Validator<String> = object : StringValidator {
override fun getTimeToLive(): TemporalAmount {
return Duration.ofHours(24)
}
}
val data = token.validateAndDecrypt(fernetKey, validator)
println(data) // my data...
with:
fun deriveKey(password: String, salt: ByteArray): String {
val iterations = 100000
val derivedKeyLength = 256
val spec = PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength)
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val key = secretKeyFactory.generateSecret(spec).encoded
return Base64.getUrlEncoder().encodeToString(key)
}
Here the Fernet key is derived using the key derivation function PBKDF2. PBKDF2 expects various input parameters, such as a password, a digest, a salt, an iteration count and the desired key length. In the posted example the key is returned Base64url encoded.For decryption the same parameters must be used as for encryption. Since the salt is usually (as in the posted code) randomly generated during encryption, it must be passed to the decryption side along with the ciphertext (note: the salt is not a secret).
The validator sets the time-to-live (by default 60s) to 24h, see here for more details.
In the posted Python code the export of the salt has to be added, e.g. by Base64url encoding it analogous to key and token (and printing it for simplicity). In practice, salt and token could also be concatenated during encryption and separated during decryption.
Update:
The encryption part is analogous:
// Generate salt
val salt = generateSalt()
println(Base64.getUrlEncoder().encodeToString(salt))
// Derive Fernet key
val key = deriveKey("my password", salt)
val fernetKey = Key(key)
// Encrypt
val data = "my data..."
val token = Token.generate(fernetKey, data)
println(token.serialise()) // the Base64url encoded token
with
fun generateSalt(): ByteArray {
val random = SecureRandom()
val salt = ByteArray(16)
random.nextBytes(salt)
return salt
}
I am trying to encrypt sensible values in my environment file using a python script. I am using Fernet. I want to encrypt only those values which are not already encrypted, making sure that there isn't any multi-level encryption.
How can I know that a value is already encrypted or decrypted in this case?
Simply attempt decryption with a TTL of None (the default). If it succeeds, then you don't need to do anything more. If it fails an InvalidToken exception will be raised which you can catch. Inside the except block you can then encrypt the file, as in the following example:
import base64
from pathlib import Path
from cryptography.fernet import Fernet, InvalidToken
key = Fernet.generate_key()
f = Fernet(key)
encrypted = f.encrypt(b'Hello world')
p1, p2 = Path('file1'), Path('file2')
p1.write_bytes(encrypted)
p2.write_bytes(base64.urlsafe_b64encode(b'\x80not encrypted'))
for example in (p1, p2):
try:
data = example.read_bytes()
f.decrypt(data, None)
except InvalidToken:
example.write_bytes(f.encrypt(data))
I have been encrypting messages with a nodejs module like so:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'd6F3Efeq';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,password)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
(from here:https://lollyrock.com/articles/nodejs-encryption/)
However, using the same secret, same algorithm and same mode I cannot decrypt this using cryptography in Python.
Here's one of the things I've tried:
crypto = AES.new(password, AES.MODE_CTR)
print(crypto.decrypt(message.decode("hex")))
Returns binary data. Encoding it in UTF-8 fails, and most interestingly:
it seems AWS.new generates a random iv each time, and the result of the decryption is different for each run!
.. Now, the nodeJS library does not return a Nonce, so I don't know what else to bring with me from the NodeJS function apart from the ciphertext and the password (secret) - any ideas?
I'am trying to find RSASSA-PSS-2048-SHA256 digital signature algorithm in python2.7.
Currently my code like this:
def calc_rsassa_pss_2048_sha256(self, data):
private_key = RSA.importKey(self.private_key)
cipher = PKCS1_v1_5.new(private_key)
h = SHA.new(data)
signature = cipher.sign(h)
return base64.b64encode(signature)
But got the signature not match error when we try to verify the generated signature.
In Java the code like this:
public static PrivateKey decodePrivateKey(String privateKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
String privateKeyRaw = trimPrivateKey(privateKeyStr);
byte[] buffer = decodeBase64(privateKeyRaw);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}
public static String sha256withRSAPSS(String privateKeyStr, String content) throws InvalidKeySpecException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
PrivateKey privateKey = decodePrivateKey(privateKeyStr);
Signature signature = Signature.getInstance("SHA256withRSA/PSS", new BouncyCastleProvider());
signature.initSign(privateKey);
signature.update(content.getBytes(CHARSET));
return encodeBase64(signature.sign());
}
I don't know what's wrong with the python signature code above. Or how to use RSASSA-PSS-2048-SHA256 algorithm in python2.7 ?
Many thanks.
On the Python piece you are using PKCS#1 v1.5 padding for signing. On the Java piece you are using PSS. As you are using different schemes it's only natural that these two won't produce the same output. Generally it is more recommended to use PSS scheme over v1.5.
I'm no Python expert, but after quick look in internet, maybe Hazmat crypto library could help you with that on Python (https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/):
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.asymmetric import padding
>>> message = b"A message I want to sign"
>>> signature = private_key.sign(
... message,
... padding.PSS(
... mgf=padding.MGF1(hashes.SHA256()),
... salt_length=padding.PSS.MAX_LENGTH
... ),
... hashes.SHA256()
... )
Edit: If Hazmat won't suit you, take a look at the approved answer here: PKCS1_PSS sign() method
I am trying to encrypt a file with a GPG key but the output is keeps being empty:
>>> import gnupg
>>> home_dir = '~/.gnupg'
>>> pgp = gnupg.GPG(gnupghome=home_dir)
>>> key = open('ff.asc', 'rb')
>>> fp = open('test.txt', 'rb')
>>> res = pgp.import_keys(key.read())
>>> res.results
[{'fingerprint': 'C3...', 'text': 'Not actually changed\n', 'ok': '0'}]
>>> enc = pgp.encrypt_file(fp, 'C3...')
>>> enc.data
b''
What am I missing here?
Also, is it possible to pass the public GPG key directly to the encryption function from a string without having to import it?
The problem may be that the imported key is not trusted. From the documentation of gnupg:
Note:
Any public key provided for encryption should be trusted, otherwise
encryption fails but without any warning. This is because gpg just
prints a message to the console, but does not provide a specific error
indication that the Python wrapper can use.
The simplest solution is to use the always_trust keyword argument of encryption functions:
always_trust (defaults to False) - Skip key validation and
assume that used keys are always fully trusted.
Thus your encryption statement should read
enc = pgp.encrypt_file(fp, 'C3...', always_trust=True)