I'm trying to decode/encode a signature with SHA256withECDSA.
I have a Java code that works fine:
public void verify() throws Exception {
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA"));
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode("your public key goes here"));
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
ecdsaVerify.initVerify(publicKey);
ecdsaVerify.update("All the webhook field values (only the values) concatenated together using a semicolon ;".getBytes("UTF-8"));
boolean result = ecdsaVerify.verify(Base64.getDecoder().decode("Signature to verify")); //Must return true
}
but I need this solution for nodejs.
when I'm creating an base64 buffer in nodejs it's getting a different results from the Java code.
I currently use cryptojs & jwa npm's.
Edit:
this is my code for nodejs:
var jwa = require("jwa");
const verifySignature = () => {
let public_key = Buffer.from("public key here", "base64");
let signature = Buffer.from("Signature_here", "base64");
let payload = "data here seperated by semicolon";
let ecdsa = jwa("ES256");
let verify = ecdsa.verify(payload, signature, public_key);
}
verifySignature();
JWA implements the algorithms used in JOSE, and in particular for ECDSA uses the signature format (aka encoding) defined in P1363 that simply contains fixed-length R and S concatenated; see steps 2 and 3 of https://datatracker.ietf.org/doc/html/rfc7518#section-3.4 . This is not the format used by most other standards, and in particular by Java, which is an ASN.1 DER-encoded SEQUENCE of two INTEGER values, which are (both) variable length (all) with tag and length prefixes.
You don't need JWA at all; builtin 'crypto' has the functionality you need:
import java.security.KeyFactory;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class SO70655734 {
public static void main (String[] args) throws Exception {
String data = "some;test;data";
String pubkey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyOFORyCVCxX+W4QZevzH6skTbp2lVDtRLV0+7ypHjX26wFtSWSe4MTI0GZjIKDOAIT8KpbqH8HXI6Wo5S6/6hg==";
String sig = "MEQCICDLwztlKOXmQLuDJ0Hh96gGAT2/wsm2ymw3CDxSDnB3AiAK7eR8+C6g/zw5TmXUX0K/pV5kjIJTCieIkQXzH30WYA==";
Signature ecdsa = Signature.getInstance("SHA256withECDSA");
ecdsa.initVerify(KeyFactory.getInstance("EC").generatePublic(
new X509EncodedKeySpec(Base64.getDecoder().decode(pubkey)) ));
ecdsa.update(data.getBytes("UTF-8")); // or StandardCharsets.UTF_8
System.out.println(ecdsa.verify(Base64.getDecoder().decode(sig)));
}
}
-> true
const crypto = require('crypto');
const data = "some;test;data";
const pubkey = "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyOFORyCVCxX+W4QZevzH6skTbp2lVDtRLV0+7ypHjX26wFtSWSe4MTI0GZjIKDOAIT8KpbqH8HXI6Wo5S6/6hg==\n"
+ "-----END PUBLIC KEY-----\n";
const sig = "MEQCICDLwztlKOXmQLuDJ0Hh96gGAT2/wsm2ymw3CDxSDnB3AiAK7eR8+C6g/zw5TmXUX0K/pV5kjIJTCieIkQXzH30WYA==";
var ecdsa = crypto.createVerify('SHA256');
ecdsa.update(data,'utf8');
console.log( ecdsa.verify(pubkey, sig,'base64') );
-> true
Note: recent versions of nodejs can instead take DER-format pubkey (without the BEGIN/END lines added, but with the base64 converted to binary), but my most convenient test system only has 8.10.0 so I went the more compatible old way.
Related
I am trying to create a PKI signature using the private key in PHP.
These are the following rules to create a signature
Use the SHA-2 algorithm to generate the hash of the Signature Base String.
Sign the hashed value using the private key of the app.
Base64-encode the signature value.
NOTE: Base64 encoding should not include the CRLF (carriage return/line feed) every 72 characters
which is part of strict Base64 encoding. Instead, the whole Base64 encoded string should be without
line breaks.
Set the string as the value for the signature parameter.
Example of Nodejs code:
var signature = crypto.createSign('RSA-SHA256')
.update(baseString)
.sign(signWith, 'base64');
Java Code
String baseString = "Constructed base string";
Signature sig = Signature.getInstance("RSA-SHA256");
sig.initSign(privateKey); // Get private key from keystone
sig.update(baseString.getBytes());
byte[] signedData = sig.sign();
String finalStr = Base64.getEncoder().encodeToString(signedData);
I am trying to convert this code into PHP,
My base string is correct.
// $data = "BaseString";
// $private_key_pem = openssl_pkey_get_private("file://".$path."privateKey.pem",'passphrase');
$hash = hash('sha256', $data);
$result = openssl_sign($hash, $signature, $private_key_pem,'RSA-SHA256');
$signature = base64_encode($signature);
Is this correct?
If yes, The API response is "Invalid PKI signature"
I setup two programs in Java and PHP to compare the output (signature = finalStr) and
the verification of the signature. To get a comparable result I hardcoded the RSA keys
in both programs so the code looks like a little bit strange.
To get shorter key strings I generated 512 bit RSA keys that are unsecure -
use a minimum of 2048 bit keys in production.
As you can see both programs generate the same signature of:
finalStr: NEHC7o+mW34qoTNOwXRQIRfs80s/YhudzX0K4AGlFTeyyJcRhit9f03iw58Ww1Eo3zfkSrrz3411TZheVLHFnQ==
and both programs can verify the signature as true.
This is the Java code:
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class MainSo {
public static void main(String[] args) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, InvalidKeySpecException {
System.out.println("https://stackoverflow.com/questions/62674669/converting-nodejs-or-java-signature-hashing-function-to-php");
// keys are sample rsa 512 keys
String privateKey1 = "-----BEGIN PRIVATE KEY-----\n" +
"MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqg8Hlhxm7LfqASjF\n" +
"KMce91anr2ViG/K8GQmk0HPMiw3Lh6DrGDGmsw2jUczwQTyv07qDwWwf+vaEiTdk\n" +
"jd1JxQIDAQABAkAOGbTtU2mNUyqJ8hF28hu1MnAw8N0TqCrEgLIzvoZFOTqvxPqc\n" +
"VaCuUs4Fm/J5x8gWLycsRmbBMeecIzvjzXY5AiEAtoZ4WSplvJbEHjiKhW+dRICc\n" +
"tSTcGaTf0v4vdfQTiGsCIQDug9wLUZDiSttbz2QlA3QthFX+UIu8fE/A/lGEjXnC\n" +
"jwIgcejRyrPO8jcVBdc7e7MAbvPk2Je8VLS0irTfYbmFRykCIQDCFsbu5vbxTlzm\n" +
"fwNNI1xc1b1sb3rmbHox4EHRjZaxfQIgEr2r53jmSRlyQfueo4nLZJhTGXdaJN8Z\n" +
"yoWwFsFqsiA=\n" +
"-----END PRIVATE KEY-----";
String publicKey1 = "-----BEGIN PUBLIC KEY-----\n" +
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoPB5YcZuy36gEoxSjHHvdWp69lYhvy\n" +
"vBkJpNBzzIsNy4eg6xgxprMNo1HM8EE8r9O6g8FsH/r2hIk3ZI3dScUCAwEAAQ==\n" +
"-----END PUBLIC KEY-----";
// rsa key generation
// Remove markers and new line characters in private key
String realPrivateKey = privateKey1.replaceAll("-----END PRIVATE KEY-----", "")
.replaceAll("-----BEGIN PRIVATE KEY-----", "")
.replaceAll("\n", "");
byte[] priKey = Base64.getDecoder().decode(realPrivateKey);
PKCS8EncodedKeySpec specPri = new PKCS8EncodedKeySpec(priKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(specPri);
// Remove markers and new line characters in public key
String realPublicKey = publicKey1.replaceAll("-----END PUBLIC KEY-----", "")
.replaceAll("-----BEGIN PUBLIC KEY-----", "")
.replaceAll("\n", "");
byte[] pubKey = Base64.getDecoder().decode(realPublicKey);
X509EncodedKeySpec specPub = new X509EncodedKeySpec(pubKey);
PublicKey publicKey = kf.generatePublic(specPub);
String baseString = "Constructed base string";
//Signature sig = Signature.getInstance("RSA-SHA256");
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey); // Get private key from keystone
sig.update(baseString.getBytes());
byte[] signedData = sig.sign();
String finalStr = Base64.getEncoder().encodeToString(signedData);
System.out.println("finalStr: " + finalStr);
// verify signature
byte[] signedDataVerify = Base64.getDecoder().decode(finalStr);
Signature sigVerify = Signature.getInstance("SHA256withRSA");
sigVerify.initVerify(publicKey);
sigVerify.update(baseString.getBytes());
boolean verified = sigVerify.verify(signedDataVerify);
System.out.println("signature verified: " + verified);
}
}
and here is the PHP-code:
<?php
// https://stackoverflow.com/questions/62674669/converting-nodejs-or-java-signature-hashing-function-to-php
$data = 'Constructed base string';
// sample 512 rsa keys
$privateKey1 = "-----BEGIN PRIVATE KEY-----\n" .
"MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqg8Hlhxm7LfqASjF\n" .
"KMce91anr2ViG/K8GQmk0HPMiw3Lh6DrGDGmsw2jUczwQTyv07qDwWwf+vaEiTdk\n" .
"jd1JxQIDAQABAkAOGbTtU2mNUyqJ8hF28hu1MnAw8N0TqCrEgLIzvoZFOTqvxPqc\n" .
"VaCuUs4Fm/J5x8gWLycsRmbBMeecIzvjzXY5AiEAtoZ4WSplvJbEHjiKhW+dRICc\n" .
"tSTcGaTf0v4vdfQTiGsCIQDug9wLUZDiSttbz2QlA3QthFX+UIu8fE/A/lGEjXnC\n" .
"jwIgcejRyrPO8jcVBdc7e7MAbvPk2Je8VLS0irTfYbmFRykCIQDCFsbu5vbxTlzm\n" .
"fwNNI1xc1b1sb3rmbHox4EHRjZaxfQIgEr2r53jmSRlyQfueo4nLZJhTGXdaJN8Z\n" .
"yoWwFsFqsiA=\n" .
"-----END PRIVATE KEY-----\n";
$publicKey1 = "-----BEGIN PUBLIC KEY-----\n" .
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoPB5YcZuy36gEoxSjHHvdWp69lYhvy\n" .
"vBkJpNBzzIsNy4eg6xgxprMNo1HM8EE8r9O6g8FsH/r2hIk3ZI3dScUCAwEAAQ==\n" .
"-----END PUBLIC KEY-----\n";
$privateKey = openssl_pkey_get_private ($privateKey1);
$publicKey = openssl_pkey_get_public($publicKey1);
// create the signature
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);
echo 'finalStr (Base64):' . PHP_EOL . base64_encode($signature) . PHP_EOL;
// verify signature
$result = openssl_verify($data, $signature, $publicKey, "sha256WithRSAEncryption");
echo 'verified (0=false, 1=true): ' . $result;
?>
In reference to this discussion: Decode South African (ZA) Drivers License
Please assist I seem to be getting an error trying to create PublicKey instance in Java on android. I have pasted the error below:
java.lang.RuntimeException: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
Here is the code snippet:
Cipher asymmetricCipher = null;
asymmetricCipher = Cipher.getInstance("RSA");
X509EncodedKeySpec publicKeySpec128 = new X509EncodedKeySpec(key128block);
X509EncodedKeySpec publicKeySpec74 = new X509EncodedKeySpec(key74block);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Key key = keyFactory.generatePublic(publicKeySpec128);
asymmetricCipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainText = asymmetricCipher.doFinal(topBlocksData[0]);
The encoded public keys you're trying to read are not of the format expected by X509EncodedKeySpec. Instead they are of an even simpler form which, unfortunately, is not supported by Java. However, you can use the Bouncycastle or Spongycastle library to code up a small java routine to parse the values. The code below is copied from my answer to this question.
import java.io.IOException;
import java.math.BigInteger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DLSequence;
public class RsaAsn1Example {
// ...
public static BigInteger [] parseASN1RsaPublicKey(byte [] encoded) throws IOException {
ASN1InputStream asn1_is = new ASN1InputStream(encoded);
DLSequence dlSeq = (DLSequence) asn1_is.readObject();
ASN1Integer asn1_n = (ASN1Integer) dlSeq.getObjectAt(0);
ASN1Integer asn1_e = (ASN1Integer) dlSeq.getObjectAt(1);
asn1_is.close();
return new BigInteger[]{ asn1_n.getPositiveValue(), asn1_e.getPositiveValue()};
}
// ....
}
The values returned by parseASN1RsaPublicKey can be supplied to an RsaPublicKeySpec constructor to create the public key.
Also examine divanov's answer to the same questions for an alternative.
I've been attempting to convert my Amazon Web Services IAM key for use with SES and I haven't been able to successfully authenticate with the code that I'm using.
I've taken the algorithm from the official documentation and attempted to convert it to Python. While I believe that I have done this correctly, this could definitely use a review.
The documentation example code (as it is written in Java) is as follows:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class SesSmtpCredentialGenerator {
private static final String KEY = "AWS SECRET ACCESS KEY"; // Replace with your AWS Secret Access Key.
private static final String MESSAGE = "SendRawEmail"; // Used to generate the HMAC signature. Do not modify.
private static final byte VERSION = 0x02; // Version number. Do not modify.
public static void main(String[] args) {
// Create an HMAC-SHA256 key from the raw bytes of the AWS Secret Access Key
SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "HmacSHA256");
try {
// Get an HMAC-SHA256 Mac instance and initialize it with the AWS secret access key.
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
// Compute the HMAC signature on the input data bytes.
byte[] rawSignature = mac.doFinal(MESSAGE.getBytes());
// Prepend the version number to the signature.
byte[] rawSignatureWithVersion = new byte[rawSignature.length + 1];
byte[] versionArray = {VERSION};
System.arraycopy(versionArray, 0, rawSignatureWithVersion, 0, 1);
System.arraycopy(rawSignature, 0, rawSignatureWithVersion, 1, rawSignature.length);
// To get the final SMTP password, convert the HMAC signature to base 64.
String smtpPassword = DatatypeConverter.printBase64Binary(rawSignatureWithVersion);
System.out.println(smtpPassword);
} catch (Exception ex) {
System.out.println("Error generating SMTP password: " + ex.getMessage());
}
}
}
and here is the code that I have written:
def hash_IAM(user_password):
#http://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html
if DEBUG: debug_message("Hashing for IAM to SES values")
#Pseudocode:
#encrypted_intent = HmacSha256("SendRawEmail", "AWS Secret Key")
#encrypted_intent = concat(0x02, encrypted_intent)
#resultant_password = Base64(encrypted_intent)
#private static final String KEY = "AWS SECRET ACCESS KEY";
AWS_SECRET_ACCESS_KEY = user_password
# private static final String MESSAGE = "SendRawEmail";
AWS_MESSAGE = "SendRawEmail"
#private static final byte VERSION = 0x02;
#see chr(2) below.
#SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "HmacSHA256");
#Mac mac = Mac.getInstance("HmacSHA256");
#mac.init(secretKey);
#If I understand correctly, SecretKeySpec is just a vessel for the key and a str that mac.init expects..
#http://developer.android.com/reference/javax/crypto/spec/SecretKeySpec.html [(key data), (algorithm)]
#http://docs.oracle.com/javase/7/docs/api/javax/crypto/Mac.html#init(java.security.Key, java.security.spec.AlgorithmParameterSpec)
#in Python 2, str are bytes
signature = hmac.new(
key=AWS_SECRET_ACCESS_KEY,
msg=AWS_MESSAGE,
digestmod=hashlib.sha256
).digest()
# Prepend the version number to the signature.
signature = chr(2) + signature
# To get the final SMTP password, convert the HMAC signature to base 64.
signature = base64.b64encode(signature)
return signature
And I've given myself very-open IAM group permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ses:*"
],
"Resource": "*"
}
]
}
I'm hoping that someone can spot an issue in my code or knows something about the API that I was not able to find via Google searches.
Update: I compiled the Java version and my output matches that of a test string. Ostensibly, my Python version is correct, but I'm still getting exceptions of type SMTPAuthenticationError.
I am writing a test harness in java for a program relating to the ikev2 protocol. As part of this i need to be able to calculate an ECDSA signature (specifically using the NIST P-256 curve).
RFC 4754 Describes the the use of ECDSA in IKEv2 and helpfully provides a set of test vectors (Including for the p256 curve that i need).
I am trying to run the ECDSA-256 Test Vector values (Section 8.1 in the RFC) through java's ECDSA signature implementation using the following code:
//"abc" for the input
byte[] input = { 0x61, 0x62, 0x63 };
//Ugly way of getting the ECParameterSpec for the P-256 curve by name as opposed to specifying all the parameters manually.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec kpgparams = new ECGenParameterSpec("secp256r1");
kpg.initialize(kpgparams);
ECParameterSpec params = ((ECPublicKey) kpg.generateKeyPair().getPublic()).getParams();
//Create the static private key W from the Test Vector
ECPrivateKeySpec static_privates = new ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), params);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(static_privates);
//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm
Signature dsa = Signature.getInstance("SHA256withECDSA");
dsa.initSign(spriv);
dsa.update(input);
byte[] output = dsa.sign();
System.out.println("Result: " + new BigInteger(1, output).toString(16));
The result should be:
CB28E099 9B9C7715 FD0A80D8 E47A7707 9716CBBF 917DD72E
97566EA1 C066957C 86FA3BB4 E26CAD5B F90B7F81 899256CE 7594BB1E A0C89212
748BFF3B 3D5B0315
Instead I get:
30460221 00dd9131 edeb5efd c5e718df c8a7ab2d 5532b85b 7d4c012a e5a4e90c 3b824ab5 d7022100 9a8a2b12 9e10a2ff 7066ff79 89aa73d5 ba37c868 5ec36517 216e2e43 ffa876d7
I know that the length difference is due to Java ASN.1 Encoding the signature. However, the rest of it is completely wrong and I'm stumped as to why.
Any help or advice would be greatly appreciated!
P.S I am not a ECDSA or Java crypto expert so it is probably a stupid mistake I am making
I'm guessing that each time you run your program, you get a different signature value for the same plaintext (to-be-signed) input.
ECDSA specifies that a random ephemeral ECDSA private key be generated per signature. To that end, Signature.getInstance("SHA256withECDSA") doesn't let you specify an ephemeral key (this is a good thing, to prevent many a self shot in the foot!). Instead, it gets its own SecureRandom instance that will make your output nondeterministic.
This probably means you can't use JCE (Signature.getInstance()) for test vector validation.
What you could do is extend SecureRandom in a way that it returns deterministic data. Obviously you shouldn't use this in a real deployment:
public class FixedSecureRandom extends SecureRandom {
private static boolean debug = false;
private static final long serialVersionUID = 1L;
public FixedSecureRandom() { }
private int nextBytesIndex = 0;
private byte[] nextBytesValues = null;
public void setBytes(byte[] values) {
this.nextBytesValues = values;
}
public void nextBytes(byte[] b) {
if (nextBytesValues==null) {
super.nextBytes(b);
} else if (nextBytesValues.length==0) {
super.nextBytes(b);
} else {
for (int i=0; i<b.length; i++) {
b[i] = nextBytesValues[nextBytesIndex];
nextBytesIndex = (nextBytesIndex + 1) % nextBytesValues.length;
}
}
}
}
Phew. Ok now you have a SecureRandom class that returns you some number of known bytes, then falls back to a real SecureRandom after that. I'll say it again (excuse the shouting) - DO NOT USE THIS IN PRODUCTION!
Next you'll need to use a ECDSA implementation that lets you specify your own SecureRandom. You can use BouncyCastle's ECDSASigner for this purpose. Except here you're going to give it your own bootlegged FixedSecureRandom, so that when it calls secureRandom.getBytes(), it gets the bytes you want it to. This lets you control the ephemeral key to match that specified in the test vectors. You may need to massage the actual bytes (eg. add zero pre-padding) to match what ECDSASigner is going to request.
ECPrivateKeyParameters ecPriv = ...; // this is the user's EC private key (not ephemeral)
FixedSecureRandom fsr_k = new FixedSecureRandom();
fsr_k.setBytes(tempKeyK);
ECDSASigner signer = new ECDSASigner();
ParametersWithRandom ecdsaprivrand = new ParametersWithRandom(ecPriv, fsr_k);
signer.init(true, ecdsaprivrand);
Note that BC's ECDSASigner implements only the EC signature part, not the hashing. You'll still need to do your own hashing (assuming your input data is in data):
Digest md = new SHA256Digest()
md.reset();
md.update(data, 0, data.length);
byte[] hash = new byte[md.getDigestSize()];
md.doFinal(hash, 0);
before you create the ECDSA signature:
BigInteger[] sig = signer.generateSignature(hash);
Finally, this BigInteger[] (should be length==2) are the (r,s) values. You'll need to ASN.1 DER-encode it, which should give you the droids bytes you're looking for.
here's my complete test following tsechin's solution using BouncyCastle but sticking to good old JCA API:
byte[] input = { 0x61, 0x62, 0x63 };
//Create the static private key W from the Test Vector
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
org.bouncycastle.jce.spec.ECPrivateKeySpec privateKeySpec = new org.bouncycastle.jce.spec.ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), parameterSpec);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(privateKeySpec);
//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm
Signature dsa = Signature.getInstance("SHA256withECDSA", "BC");
FixedSecureRandom random = new FixedSecureRandom();
random.setBytes(Hex.decode("9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE"));
dsa.initSign(spriv, random);
dsa.update(input);
byte[] output = dsa.sign();
// compare the signature with the expected reference values
ASN1Sequence sequence = ASN1Sequence.getInstance(output);
DERInteger r = (DERInteger) sequence.getObjectAt(0);
DERInteger s = (DERInteger) sequence.getObjectAt(1);
Assert.assertEquals(r.getValue(), new BigInteger("CB28E0999B9C7715FD0A80D8E47A77079716CBBF917DD72E97566EA1C066957C", 16));
Assert.assertEquals(s.getValue(), new BigInteger("86FA3BB4E26CAD5BF90B7F81899256CE7594BB1EA0C89212748BFF3B3D5B0315", 16));
For my current project I have to send a signature from PHP to Java application. I am using Crypt/RSA right now for signing my data.
For test I am signing just "abc" with following code :
$rsa = new Crypt_RSA();
$plaintext = 'abc';
$rsa->loadKey("MIICXgIBAAKBgQDjh+hNsqJe566JO0Sg7Iq5H1AdkauACdd8QMLp9YNY0HPslVH0
rXaOFo0zgH0Ktu/Ku3lS1lfxbFQAY8b6ywZKvu4eoxlnEwuBwy09CG+3ZiVLBjCj
TZHA/KOkpVLa+tA6KsoP6zv/xI/ACkSCxPGR0q3SiRuhXV/6tacoKxUYnwIDAQAB
AoGBAIC00GOjONYWmFRoglnFdHNjkx4m2KyE5LAUsi1GBBapU+nwTXvq47VcbGNF
u3XkJaC4i9igBv86GApgZp5XWia86On/Lz9NR4fB2EFP6Ydy84GfCDNNvkism4BR
aA+eYdNiQ3Wfyi98ZpUi+rPsoI6Cid4eSkCC4poTUaqzMkiBAkEA9Gn1oIlUEoVI
q/u5Y9vflXRDt95AA9AokJkQj7XTNjsz8ypU8TO6D6ZykpcbK6zjU0UJsQiC3dKj
AgmAR2VzYwJBAO5RETMAyDnR+5g+MtHpwGqGdY4dq0j4y4CsdtOYKWwSTh3VQy+C
eghJoyPRfIpulw2Mk/l+occEI0ohJl0+UJUCQQDSZtjVLwMZwnUx4EvSw/ewL9sP
0Jpo7evNtoaEQDEncUWiYeGnljDowg/FU6FHMtiq2TajmMEXdflvioBMdfAjAkEA
3TB60SbJr/i4Fo6sJm5ZO8W+eAALiTf50VzBERTqZTb8L+5PZFoqn2SROV5mxClu
o5G1idzBlHC/vD7WV7bNnQJAd0FrxaMBurJ4Uv/B8TDP+eeBdB7d9rKw0+TVlcel
cbpIz6BIP6+nmsgy6dbDRnx0eC/MgF2EU0wrCu1DK0PyWA==");
$rsa->setHash("sha256");
$signature = $rsa->sign($plaintext);
$signature_encoding = mb_convert_encoding($signature, "UTF-8");
error_log("signature encoded in UTF-8 :" . $signature_encoding);
$encoded_sign = base64_encode($signature_encoding);
error_log("encoded sign for abc: " . $encoded_sign);
I can verify the signature from php code. But when it comes to verifying from JAVA, i was not successfull. Here is the java code that does the verify operation :
public boolean verify(String signed, String data, PubKey pubKey) throws Exception{
PublicKey publicKey = jceProvider.generateRSAPublicKeyFromX509(
base64.decode(pubKey.getEncodedKey())
);
byte[] signature = base64.decode(signed);
byte[] verifier = data.getBytes(Charset.forName("UTF-8"));
return jceProvider.verify(signature, verifier, publicKey);
}
public class JCEProvider {
public boolean verify (byte[] signature, byte[] verifier, PublicKey publicKey) throws Exception{
Signature rsaSignature = Signature.getInstance("SHA256withRSA");
rsaSignature.initVerify(publicKey);
rsaSignature.update(verifier);
return rsaSignature.verify(signature);
}
I dont think it is because of keys, I can already verify it from PHP as I told before. There is something that I miss about PHP encoding or byte streams but I am lost for the moment.
Any help would be appreciated.
I'm using openssl like Whity already mentioned. Here is my striped down example. Be aware of any character encoding, line ending, etc. This results in changed binary representation of your text data.
PHP-RSA_SHA256-Sign:
<?php
$data = "For my current project I have to send a signature from PHP to Java application. I am using Crypt/RSA right now for signing my data.";
$private_key = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6zxqlVzz0wy2j4kQVUC4Z
RZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQJAL151ZeMKHEU2c1qdRKS9
sTxCcc2pVwoAGVzRccNX16tfmCf8FjxuM3WmLdsPxYoHrwb1LFNxiNk1MXrxjH3R
6QIhAPB7edmcjH4bhMaJBztcbNE1VRCEi/bisAwiPPMq9/2nAiEA3lyc5+f6DEIJ
h1y6BWkdVULDSM+jpi1XiV/DevxuijMCIQCAEPGqHsF+4v7Jj+3HAgh9PU6otj2n
Y79nJtCYmvhoHwIgNDePaS4inApN7omp7WdXyhPZhBmulnGDYvEoGJN66d0CIHra
I2SvDkQ5CmrzkW5qPaE2oO7BSqAhRZxiYpZFb5CI
-----END RSA PRIVATE KEY-----
EOD;
$binary_signature = "";
$algo = "SHA256";
openssl_sign($data, $binary_signature, $private_key, $algo);
print(base64_encode($binary_signature) ."\n");
?>
The output of base64 encoded binary signature is:
OnqiWnFQ2nAjOa1S57Du9jDpVr4Wp2nLdMk2FX+/qX1+SAHpVsW1JvQYqQUDlxvbTOE9vg6dlU6i3omR7KipLw==
JAVA-RSA_SHA256-Verify:
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import org.apache.commons.codec.binary.Base64;
public class RsaVerify {
public static void main(String args[]){
String publicKey =
// "-----BEGIN PUBLIC KEY-----"+
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6"+
"zxqlVzz0wy2j4kQVUC4ZRZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQ==";
// "-----END PUBLIC KEY-----";
byte[] data = "For my current project I have to send a signature from PHP to Java application. I am using Crypt/RSA right now for signing my data.".getBytes();
byte[] signature = Base64.decodeBase64("OnqiWnFQ2nAjOa1S57Du9jDpVr4Wp2nLdMk2FX+/qX1+SAHpVsW1JvQYqQUDlxvbTOE9vg6dlU6i3omR7KipLw==");
try {
System.out.println(verify(data, signature, publicKey));
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
private static boolean verify(byte[] data, byte[] signature, String publicKey) throws GeneralSecurityException{
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(data);
return sig.verify(signature);
}
}
phpseclib uses the more secure PSS padding by default. Java is probably using PKCS#1 padding. So if you were to go the phpseclib route (which I'd recommend doing)... do this:
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
I think u need to improve your PHP solution.
According to http://php.net/manual/en/function.openssl-get-md-methods.php you can use directly [47] => sha256WithRSAEncryption from PHP, probably call openssl from commandline also be possible:
openssl dgst -sha256 -sign my.key -out in.txt.sha256 in.txt