I'm trying to sign an encrypted message with a private key and verify it in Java. This is my first time working with encryption and signatures so I'm not sure how it is supposed to work and I'm kind of stuck here. The verification always returns false.
Here I sign the message:
public byte[] rsaSign (byte[] data) {
byte[] cipherData = null;
try {
RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(signModulus, signExponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey privKey = fact.generatePrivate(keySpec);
Signature s = Signature.getInstance("SHA1withRSA");
s.initSign(privKey);
s.update(data);
return s.sign();
}
return cipherData;
}
And here I try to verify the signature:
public boolean rsaVerify (byte[] data, byte[] signature) {
boolean success = false;
try {
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(signModulus, signPublicExponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(keySpec);
Signature s = Signature.getInstance("SHA1withRSA");
s.initVerify(pubKey);
s.update(data);
success = s.verify(signature);
return success;
}
return false;
}
Can anyone see a problem? The keys are generated in C# and converted to BigIntegers in java.
Signature verification is failed because you are using a different public key in the verification method.
Use the public key to verify the signature which is consistent with the private key that is used into rsaSign() method.
Hope this will help you. Note that, this public key is consistent with the private key which is used in Signature Generation method :
/**
* This method will sign message with RSA 2048 key
* #return Void
*/
public void rsaSign (String message) throws Exception {
//key generation
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(2048, random);
KeyPair keyPair = keyGen.generateKeyPair();
PrivateKey priv = keyPair.getPrivate();
PublicKey pub = keyPair.getPublic();
System.out.println("RSAPub key Mod for Sign/Verify : " + Helper.toHex(((RSAPublicKey)pub).getModulus().toByteArray()));
System.out.println("RSAPub key Exp for Sign/Verify : " + Helper.toHex(((RSAPublicKey)pub).getPublicExponent().toByteArray()));
//sign
Signature dsa = Signature.getInstance(signALG);
dsa.initSign(priv);
dsa.update(Helper.toByte(message));
byte[] realSig = dsa.sign();
System.out.println("RSA Sign-Data : " + Helper.toHex(realSig));
}
/**
* This method verify signature with RSA public key
* #param message The plain message
* #param rsaMOD RSA Public key Modulus in string
* #param rsaEXP RSA Public key Exponent in string
* #param rsaSignData Signature which will be verified
* #return true if verifications success, false otherwise
*/
public boolean rsaVerify(String message, String rsaMOD, String rsaEXP, String rsaSignData) throws Exception {
BigInteger modBigInteger = new BigInteger(Helper.toByte(rsaMOD));
BigInteger exBigInteger = new BigInteger(Helper.toByte(rsaEXP));
RSAPublicKeySpec spec = new RSAPublicKeySpec(modBigInteger, exBigInteger);
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey publicKey = factory.generatePublic(spec);
Signature signature = Signature.getInstance(signALG);
signature.initVerify(publicKey);
signature.update(Helper.toByte(message));
return signature.verify(Helper.toByte(rsaSignData));
}
You should try and test these things locally first, with your own generated key pair. If that fails your code is wrong - it's a very simple wrapper around Java Signature so that's not at all that likely.
You already used a complete specification of the signature algorithm, so provider defaults are not an issue here.
Then check the correctness of the data on both sides by printing it out in Hex or Base64 right before signature generation/verification. If that fails you've got an I/O or encoding/decoding error. Encoding/decoding errors & string handling make up about 30% of the total of cryptography related questions!
Finally you could obtain and compare the modulus of the private and public key. If the moduli don't match then you're using a private and public key of a different key pair, and signature verification will of course always fail.
Related
I had generate public key using Java Spring Security, but I can not use that public key to encrypt the data using Nodejs crypto library. I think it is because of its format(X509).
My Nodejs code
module.exports.encryptRsa = (toEncrypt, pemPath) => {
let absolutePath = path.resolve(pemPath);
let publicKey = fs.readFileSync(absolutePath, "utf8");
let buffer = Buffer.from(toEncrypt);
let encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
My Java code
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlgorithm);
keyGen.initialize(2048);
KeyPair keyPair = keyGen.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
String formatPrivate = privateKey.getFormat(); // PKCS#8
String formatPublic = publicKey.getFormat(); // X.509
FileWriter fos = new FileWriter("publicKey.pem");
fos.write("-----BEGIN RSA PUBLIC KEY-----\n");
fos.write(enc.encodeToString(publicKeyBytes));
fos.write("\n-----END RSA PUBLIC KEY-----\n");
fos.close();
Java's getEncoded() method returns the public key in format called 'spki' by Node crypto. Java's name for that format is "X.509", an unfortunate choice because it causes confusion with certificates of that name.
The proper PEM header for spki keys is simply -----BEGIN PUBLIC KEY-----. Just get rid of RSA in the header and footer.
I am currently working on a tool for asymmetric encryption with RSA:
PublicKey FrKey;
public byte[] encrypt(String msg) {
byte[] msg1 = null;
Cipher cp = Cipher.getInstance("RSA");
cp.init(Cipher.ENCRYPT_MODE, FrKey);
msg1 = cp.doFinal(msg.getBytes());
return msg1;
}
public void setOpKey(String s) {
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(s.getBytes()));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
System.out.println("Friends Public: "+s+"\n\n"+publicKey);
}
While the setOpKey function, which is supposed to set the opponents Public Key, seems to work just fine, when the encryption class is run java returns an error:
java.security.InvalidKeyException: No installed provider supports this key: (null)
From this Error you would read, that the Key would be null, but as seen in the console output below it isn't.
Friends Public: /String I entered, generated from the RSA/
Sun RSA public key, 1024 bits
modulus: /Very long number/
public exponent: 65537
This seems to be an error with the Public Key, but I have no clue how that could be fixed. Any help would be appreciated.
PS: I removed all try/catch clauses to make the code more readable.
I'm trying to verify some signed content using ECDSA and spongycastle. Here is the code I'm using to generate the keypair with the brainpoolP512t1 ec curve:
public static KeyPair getKeyPairbrainpoolP512t1() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "SC");
ECNamedCurveParameterSpec curveParameterSpec = ECNamedCurveTable.getParameterSpec("brainpoolP512t1");
keyPairGenerator.initialize(curveParameterSpec, new SecureRandom());
return keyPairGenerator.generateKeyPair();
}
Here is the code I'm using to sign and verify signatures:
private byte[] ecdsaSign(ECPrivateKey key, byte[] content) throws Exception {
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", "SC");
ecdsaSign.initSign(key);
ecdsaSign.update(content);
byte[] signature = ecdsaSign.sign();
return signature;
}
public static boolean ecdsaVerify(ECPublicKey key, byte[] content, byte[] signature) throws Exception {
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", "SC");
ecdsaVerify.initVerify(key);
ecdsaVerify.update(content);
boolean result = ecdsaVerify.verify(signature);
return result;
}
I'm passing in the bytes of a simple string message that was signed using the private key, and also the public key in order to verify. I'm always getting false however. What am I doing wrong? Any help is deeply appreciated.
Figured out what was wrong. I was exporting the keys to PEM in order to have them in string format prior to calling sign and verify, and then decoding back to private key original format. When I omitted this conversion and called Verify directly with the private key (without the PEM string back and forth conversion), the content got verified.
I would like to generate a digital signature in my java/android project with a private key(RSA) stored in DB.
My 2 keys was generated with the below code (project is in production and I cannot change it):
// Get keys pair (RSA)
KeyPair rsaKyePair = createKeyPair();
// Get private/ public keys and store them in DB
String pri = getPrivateKeyBase64Str(rsaKyePair);
String pub = getPublicKeyBase64Str(rsaKyePair));
public static KeyPair createKeyPair() {
KeyPair keyPair = null;
try {
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(KEY_LENGTH);
keyPair = keygen.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
return keyPair;
}
public static String getPrivateKeyBase64Str(KeyPair keyPair){
if (keyPair == null) return null;
return getBase64StrFromByte(keyPair.getPrivate().getEncoded());
}
public static String getPublicKeyBase64Str(KeyPair keyPair){
if (keyPair == null) return null;
return getBase64StrFromByte(keyPair.getPublic().getEncoded());
}
public static String getBase64StrFromByte(byte[] key){
if (key == null || key.length == 0) return null;
return new String(Base64.encode(key));
}
Based on different sites (here and here), I'll try to write code for generate a signature:
String mySignature = getDigitalSignature("my_string_", "my_private_string" );
/*
* Generated a signed String
* #param text : string to sign
* #param strPrivateKey : private key (String format)
*/
public String getDigitalSignature(String text, String strPrivateKey) {
try {
// Get private key from String
PrivateKey pk = loadPrivateKey(strPrivateKey);
// text to bytes
byte[] data = text.getBytes("UTF8");
// signature
Signature sig = Signature.getInstance("MD5WithRSA");
sig.initSign(pk);
sig.update(data);
byte[] signatureBytes = sig.sign();
return javax.xml.bind.DatatypeConverter.printBase64Binary(signatureBytes);
}catch(Exception e){
return null;
}
}
private PrivateKey loadPrivateKey(String key64) throws GeneralSecurityException {
byte[] clear = Base64.decode(key64, Base64.DEFAULT);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clear);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey priv = fact.generatePrivate(keySpec);
Arrays.fill(clear, (byte) 0);
return priv;
}
For verify the signature, I use this code in my java API :
/*
* Verify signature of a string
* #param signature : signature
* #param origina: original string to verify
* #param publicKey: user public key
*/
public static boolean verfiySignature(String signature, String original, String publicKey){
try{
// Get private key from String
PublicKey pk = loadPublicKey(publicKey);
// text to bytes
byte[] originalBytes = original.getBytes("UTF8");
//signature to bytes
//byte[] signatureBytes = signature.getBytes("UTF8");
byte[] signatureBytes =javax.xml.bind.DatatypeConverter.parseBase64Binary(signature);
Signature sig = Signature.getInstance("MD5WithRSA");
sig.initVerify(pk);
sig.update(originalBytes);
return sig.verify(signatureBytes);
}catch(Exception e){
e.printStackTrace();
Logger log = Logger.getLogger(RsaCipher.class);
log.error("error for signature:" + e.getMessage());
return false;
}
}
/*
* Generate a PublicKey object from a string
* # key64 : public key in string format (BASE 64)
*/
private static PublicKey loadPublicKey(String key64) throws GeneralSecurityException {
byte[] data = javax.xml.bind.DatatypeConverter.parseBase64Binary(key64);
X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
KeyFactory fact = KeyFactory.getInstance("RSA");
return fact.generatePublic(spec);
}
I've run this code with real data, but the "verifySignature" always returns "False".
I am a newbie in Encryption world, forgive me for my dirty code.
--- EDIT
I got an exception when the verify method is called:
java.security.SignatureException: Signature encoding error
When signing you returned your signature base64-encoded:
return Base64.encodeToString(signatureBytes, Base64.DEFAULT);
Thus, when verifying you have to base64-decode the signature string. But what you do is:
byte[] signatureBytes = signature.getBytes("UTF8");
So the signatureBytes you try to verify are completely different from the signatureBytes you had as a result of signing.
You sign using
Signature sig = Signature.getInstance("RSA");
But You verify using
Signature sig = Signature.getInstance("MD5WithRSA");
Obviously you should use the same algorithm in both cases.
Background:
I have created an applet to extract public key of a certificate extracted from a smart card.
This public key is then stored in database.
The private key of certificate is used to sign data and the public key is then used to verify the signature.
Code for extracting public key from certificate:
private byte[] getPublicKey(KeyStore paramKeyStore)
throws GeneralSecurityException {
Enumeration localEnumeration = paramKeyStore.aliases();
if (localEnumeration.hasMoreElements()) {
String element = (String) localEnumeration.nextElement();
Certificate[] arrayOfCertificate =
paramKeyStore.getCertificateChain(element);
byte[] publicKeyByteArray =
arrayOfCertificate[0].getPublicKey().getEncoded();
return publicKeyByteArray;
}
throw new KeyStoreException("The keystore is empty!");
}
This publicKeyByteArray is then storeed in database as BLOB after converting to string using bytes2String method:
private static String bytes2String(byte[] bytes) {
StringBuilder string = new StringBuilder();
for (byte b : bytes) {
String hexString = Integer.toHexString(0x00FF & b);
string.append(hexString.length() == 1 ? "0" + hexString : hexString);
}
return string.toString();
}
The content of the BLOB(key) saved in database is:
30820122300d06092a864886f70d01010105000382010f003082010a02820101009bd307e4fc38adae43b93ba1152a4d6dbf82689336bb4e3af5160d16bf1599fe070f7acbfefd93e866e52043de1620bd57d9a3f244fb4e6ef758d70d19e0be86e1b12595af748fbc00aad9009bd61120d3348079b00af8462de46e254f6d2b092cbc85c7f6194c6c37f8955ef7b9b8937a7e9999541dbbea8c1b2349c712565482dbd573cd9b7ec56a59e7683b4c246620cf0d8148ed38da937f1e4e930eb05d5b4c6054712928fa59870763468c07e71265525e1e40839b51c833579f5742d3c8e0588766e3ed6deef1593b10baad0a2abea34734de1505d37710e1cfaa4225b562b96a6a4e87fecb1d627d4c61916e543eba87054ee9212e8183125cdb49750203010001
After reading the stored public key byte[] from database, I try to convert it back to Public Key using following code:
Cipher rsa;
rsa = Cipher.getInstance("RSA");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pkey.getBytes());
PublicKey pk = keyFactory.generatePublic(publicKeySpec);
rsa.init(Cipher.DECRYPT_MODE, pk);
byte[] cipherDecrypt = rsa.doFinal(encryptedText.getBytes());
but it gives following error:
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(X509Key.java:387)
at sun.security.x509.X509Key.decode(X509Key.java:403)
at sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:83)
at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:298)
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:201)
Please suggest the reason and resolution for this issue.
You must have an error in the way you read the key back from the database. The following code works just fine for me:
String key = "3082012230..."; // full key omitted for brevity
byte[] derPublicKey = DatatypeConverter.parseHexBinary(key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(derPublicKey);
keyFactory.generatePublic(publicKeySpec);
I would guess, based on the use of pkey.getBytes(), that you've simply tried to get the bytes from the string rather than hex-decoding it.