I create to python3 application that generate the RSA key pairs.
from Crypto.PublicKey import RSA
print("--Private Key Generate--")
key = RSA.generate(2048)
private_key = key.export_key()
file_out = open("key/private.pem", "wb")
file_out.write(private_key)
file_out.close()
print("--Public Key Generate--")
public_key = key.publickey().export_key()
file_out_1 = open("key/receiver.pem", "wb")
file_out_1.write(public_key)
file_out_1.close()
print("key Generated")
I sign some data using python and create a signature. It is also verified using python successfully.
def sign(data):
private_key = RSA.import_key(open('key/private.pem').read())
h = SHA256.new(data)
signature = base64.b64encode(pss.new(private_key).sign(h))
print("signature generate")
verify(data,signature)
return signature
def verify(recive_Data ,signature):
public_key = RSA.import_key(open('key/receiver.pem').read())
h = SHA256.new(recive_Data)
verifier = pss.new(public_key)
try:
verifier.verify(h, base64.b64decode(signature))
print("The signature is authentic")
except (ValueError, TypeError):
print ("The signature is not authentic.")
But actually, my verification implementation in Android(min SDK 23, target SDK 29). So, I need to convert this verification code to Android. I tried using the following code, but not verification success. need some expert help to do it.
public class SecurityHelper {
private static String getKey(InputStream filename) throws IOException {
// Read key from file
String strKeyPEM = "";
BufferedReader br = new BufferedReader(new InputStreamReader(filename));
String line;
while ((line = br.readLine()) != null) {
strKeyPEM += line + "\n";
}
br.close();
// System.out.println(strKeyPEM);
return strKeyPEM;
}
public static PublicKey getPublicKey(InputStream filename) throws IOException, GeneralSecurityException {
String publicKeyPEM = getKey(filename);
return getPublicKeyFromString(publicKeyPEM);
}
public static PublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
System.out.println(publicKeyPEM);
byte[] encoded = Base64.decode(publicKeyPEM ,Base64.CRLF);
// System.out.println(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(new X509EncodedKeySpec(encoded));
System.out.println(pubKey);
return pubKey;
}
public static boolean verify(PublicKey publicKey, String message, String signature) throws SignatureException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException, InvalidAlgorithmParameterException {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(publicKey);
sign.update(message.getBytes("UTF-8"));
System.out.println(message);
return sign.verify(Base64.decode(signature,Base64.CRLF));
}
}
There are different paddings used, in the Python code PSS and in the Android code Pkcs#1 v1.5, see for the difference RFC 8017. Replace in the Android code SHA256withRSA with SHA256withRSA/PSS.
Update:
Although according to the Android documentation SHA256withRSA/PSS is supported from API level 23+, an InvalidKeyException (No provider supports the provided key) is thrown for API level 23, for API level 24+ it works as specified.
A possible workaround for API level 23 is to use BouncyCastle, which then has to be included as a dependency in the Android project (details depend on the IDE, e.g. here for Android Studio):
implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
Before adding the BC Provider, the pre-installed version must be removed. The schema to be used is SHA256withRSAandMGF1 (see section Signature Algorithms):
Security.removeProvider("BC");
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Signature sign = Signature.getInstance("SHA256withRSAandMGF1");
// Go ahead as for schema SHA256withRSA/PSS...
Note: SpongyCastle would be an alternative possibility. Here the pre-installed BC Provider doesn't have to be removed. The schema is SHA256withRSA/PSS.
Related
I want to implement the following function using python, it uses public key to encrypt the password and then using base64 to encode it. I have searched many answers but I didn't find a solution, I think probably I didn't understand them. Would you please give me some advice?
// # java code
public static String encryptRSA(String password, String publicKey) {
try {
byte[] decoded = Base64.decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.encode(cipher.doFinal(password.getBytes("UTF-8")));
return outStr;
} catch (Exception e) {
}
return null;
}
I use the following code, but it raises error M2Crypto.RSA.RSAError.
# python code
import base64
import M2Crypto
pub = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClnY+3rAf/az9t2bxC80TObpZ2ZCH1xSjPt9QtXl6k6UtD7mQcI2CWSwnDgKJr2m2WnM1kR6X+oOL61lXO0gSuuD8tWOx/knZA2VaSTLdsHBDLOX3e6Fo/O3CtoLVwO5FYFBIFHXMoikPkR8tFIOLWsX0y3slLQQShwSJAHytP4QIDAQAB"
password = 123
def public_encrypt(public_key, password):
rsa_pub = M2Crypto.RSA.load_key_string(public_key.encode('utf-8'))
ctxt_pub = rsa_pub.public_encrypt(password.encode(), M2Crypto.RSA.pkcs1_padding)
ctxt64_pub = base64.b64encode(ctxt_pub).decode()
return ctxt64_pub
res = public_encrypt(pub, password)
print('res:', res)
The full error message is: M2Crypto.RSA.RSAError: no start line, indicating that the key is imported incorrectly.
load_key_string() requires a PEM-encoded private key in PKCS#1 or PKCS#8 format, but no public key. A PEM encoded public key in X.509/SPKI format can be imported with load_pub_key_bio().
From the posted X.509/SPKI key a PEM encoded key can be created by adding a newline after every 64 characters and adding a corresponding header and footer:
import M2Crypto
x509 = '''-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClnY+3rAf/az9t2bxC80TObpZ2
ZCH1xSjPt9QtXl6k6UtD7mQcI2CWSwnDgKJr2m2WnM1kR6X+oOL61lXO0gSuuD8t
WOx/knZA2VaSTLdsHBDLOX3e6Fo/O3CtoLVwO5FYFBIFHXMoikPkR8tFIOLWsX0y
3slLQQShwSJAHytP4QIDAQAB
-----END PUBLIC KEY-----'''
bio = M2Crypto.BIO.MemoryBuffer(x509)
rsa_pub = M2Crypto.RSA.load_pub_key_bio(bio)
Another issue is that there is no encode() defined for an integer, but presumably password is supposed to be a string and this is just a copy/paste error.
With these changes the code works.
Note that a 1024 bits RSA key (like yours) is insecure nowadays, instead the length should be >= 2048 bits.
Stuck at this point for last few days. A file is signed in python using pss(RSA) approach.
Below is the link for reference:
https://pycryptodome.readthedocs.io/en/latest/src/signature/pkcs1_pss.html
At Java client side, while verification, below mentioned approach is used:
https://pycryptodome.readthedocs.io/en/latest/src/faq.html#are-rsassa-pss-signatures-compatible-with-java-or-openssl
Signing Approach:
A hash value has been generated from the payload and the hash value is signed using pem private key. The signature bytes are then encoded to Base64 and sent to client for verification.
Verification Approach:
Initially, a hash value is generated from the payload and then the signature bytes as received from the server side is decoded from Base64 and used for verification.
Both the hash values, viz, sent from Server side and generated at Client side, both are same. However, still the signature is unable to be verified.
Code snippet:
Signing in python:
def sign_file(rsa, byte_buffer):
mgf = lambda x, y: pss.MGF1(x, y, SHA256)
h = SHA256.new(byte_buffer.getbuffer())
print("Hash value: " ,h.hexdigest())
signer = pss.new(rsa, mask_func=mgf, salt_bytes=len(h.digest()))
print("Salt length: ", signer._saltLen)
signature = b64encode(signer.sign(h))
return signature
# SIGN
rsa_key = get_key(priv_key_file)
signature = sign_file(rsa_key, encrypted)
Verification in Java:
public static String verify(String signedData, String signedHash, boolean debug, PublicKey publickey) {
boolean successFlg = false;
try {
Security.addProvider(new BouncyCastleProvider());
Signature ss = Signature.getInstance("SHA256withRSA/PSS");
AlgorithmParameters pss1 = ss.getParameters();
PSSParameterSpec pssParameterSpec = new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, 1);
ss.setParameter(pssParameterSpec);
ss.initVerify(publickey);
ss.update(signedHash.getBytes());
successFlg = ss.verify(Base64.getDecoder().decode(signedData.getBytes()));
if (debug) {
System.out.println( "signature matched: " + successFlg);
}
} catch (Exception e) {
e.printStackTrace();
}
return String.valueOf(successFlg);
}
Salt length: 32
Generated hash value:
7ff8ce3dab21c615764682394c07fbc858a5713002f68c1fce520a3a93adbecd
Signed Data(as received from Server side)(Base64 encoded)
E459mLf2PeAIMPFy4c3md+BT+AFMtDBsoyP1GjDgbMFc72Ccuvy9Y97vrjYJxilfaOtdaCdo8xQWURnaWjEdHRT3VL/A5bfqEata2L2v8DxnD7hPtSr5tRYVRvsTDm5ByJHR9uh0VwQTz19GOjvG/slHMH0uJJXSOpuMB/brZggMUspY87oGDDcL+z1JuI6elVQtw+weP2kWWQ5gIG/+WbT2MXiKptaq0cqq6sG5oQNni42KCUj2+HADqypqdlVuyk02CgYDe69WQwnb90qILdqiqnyIbRlhoT8Du/DHY+N15ShTUBght6oeqj/cfuUeMtVgIdIP3g+gH/JMdl2sdw==
Please help in the case.
I am trying to sign a piece of text using a private key using SwiftyRSA, which I do successfully. Then send the signature to the python server which holds the public key for verification. the But I keep receiving the InvalidSignature exception.
I have tried different hashing algorithms, bit sizes, but still the same InvalidSignature exception. I am sure that the key and signature are related b/c they are generated at the same time!
My issue is not with the libraries themselves--which I think are functioning properly. I think it has to do with a padding discrepancy between the two libraries. Cryptography is using PSS, while I cant find the padding/salt that SwiftyRSA uses.
Key Verification Script
#app.route('/', methods=['POST'])
def verify():
record = json.loads(request.data)
signature = record['sig']
public_key = record['public_key']
hash_local_token = record['hash_local_token']
input_string = record['input_string']
verification_response = auth.verify_signature(signature=signature, pub_key=public_key, input_string=input_string)
print(verification_response)
return jsonify({"Verification": verification_response})
def verify_signature(signature, pub_key, input_string):
# Load the public key
# Url Safe Base64 Decoding
derdata = base64.b64decode(pub_key)
public_key = load_der_public_key(derdata, default_backend())
signature_decoded = base64.urlsafe_b64decode(signature)
# Perform the verification.
try:
public_key.verify(
signature_decoded,
str.encode(input_string),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
),
hashes.SHA256(),
)
return "SUCCESS: Signature Verified!"
except cryptography.exceptions.InvalidSignature as e:
return 'FAILED: Payload and/or signature files failed verification'
Key Generation Script
func moreTesting() {
do {
let keyPair = try SwiftyRSA.generateRSAKeyPair(sizeInBits: 4096)
let privateKey = keyPair.privateKey
let publicKey = keyPair.publicKey
let inputString = "test_string"
let clear = try ClearMessage(string: inputString, using: .utf8)
let signature = try clear.signed(with: privateKey, digestType: .sha256)
let base64Signature = signature.base64String
let parameters: [String: String] = [
"sig": base64Signature,
"input_string": inputString,
"public_key": try publicKey.base64String()
]
AF.request("http://127.0.0.1:5000/", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default).responseJSON { response in
debugPrint(response)
}
}
catch {
print(Error.self)
}
}
It appears that SwiftyRSA's signing APIs use only PKCS1 and not PSS. See: https://github.com/TakeScoop/SwiftyRSA/blob/master/Source/Signature.swift#L20-L27
So to resolve this you'll want to switch your padding from PSS to PKCS1v15 on the Python side.
I looked at python-jose and jose but neither seem to support encrypting a signed JWT. For example, "jose" library supports signing and encrypting separately, without nesting them.
Am I missing something, like perhaps it's fairly easy to nest JWTs outside the library? If so, please share tips on achieving this so the result is well formatted.
jwcrypto supports nested JWS and JWE.
To sign and then encrypt:
# Load your RSA pub and private keys
pubKey = jwk.JWK().from_pyca(serializedPublicKey)
privateKey = jwk.JWK().from_pyca(serializedPrivateKey)
# your JWT claims go here
claims = {
# JWT claims in JSON format
}
# sign the JWT
# specify algorithm needed for JWS
header = {
u'alg' : 'RS256',
'customSigHeader':'customHeaderContent'
}
# generate JWT
T = jwt.JWT(header, claims)
# sign the JWT with a private key
T.make_signed_token(privateKey)
# serialize it
signed_token = T.serialize(compact=True)
# JWE algorithm in the header
eprot = {
'alg': "RSA-OAEP",
'enc': "A128CBC-HS256",
'customEncHeader':'customHeaderContent'
}
E = jwe.JWE(signed_token, json_encode(eprot))
# encrypt with a public key
E.add_recipient(pubKey)#
# serialize it
encrypted_signed_token = E.serialize(compact=True)
To decrypt and verify signature:
#Decrypt and Verify signature
E = jwe.JWE()
# deserialize and decrypt
E.deserialize(encrypted_signed_token, key=privateKey)
raw_payload = E.payload
# verify signature
S = jws.JWS()
S.deserialize(raw_payload, key=pubKey)
final_payload = S.payload
I have pkcs8_rsa_private_key file which generate by openssl from a rsa_private_key.pem file.
I need make a signature by the private key in python, make the same signature with below java code.
public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
public static String sign(String content, String privateKey) {
String charset = "utf-8";
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
Base64.decode(privateKey));
KeyFactory keyf = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
java.security.Signature signature = java.security.Signature
.getInstance(SIGN_ALGORITHMS);
signature.initSign(priKey);
signature.update(content.getBytes(charset));
byte[] signed = signature.sign();
return Base64.encode(signed);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
PKCS#8 defines a way to encode and transport secret keys and it is not specific to OpenSSL; PKCS#1 defines a way to use an RSA key (no matter how it was loaded into your application, with PKCS#8 or not) to carry out and verify digital signature on data.
The piece of code you have does three things:
It decodes Base64 into PKCS#8
It decodes PKCS#8 into the actual key in memory (mind you may need to provide a passphrase here)
It performs PKCS#1 v1.5 signature using SHA-1 using said key
It encodes the signature in Base64
The example for PKCS#1 v1.5 signing in the API of PyCrypto does exactly steps #2 and #3.
from Crypto.Signature import PKCS1_v1_5 as pk
from Crypto.Hash import SHA
class Crypt(object):
pkcs8_private_key = RSA.importKey(open('pkcs8_rsa_private_key.pem', 'r').read())
def rsa_sign(cls, des_reqdata):
"""
#:param reqdata: request reqData
"""
h = SHA.new(des_reqdata)
signer = pk.new(cls.pkcs8_private_key)
signature = signer.sign(h)
return base64.b64encode(signature)