Using Sha1 and Rsa with Java.Security.Signature VS. Messagedigest and Cipher

Using SHA1 and RSA with java.security.Signature vs. MessageDigest and Cipher

OK, I've worked out what's going on. Leonidas is right, it's not just the hash that gets encrypted (in the case of the Cipher class method), it's the ID of the hash algorithm concatenated with the digest:

  DigestInfo ::= SEQUENCE {
digestAlgorithm AlgorithmIdentifier,
digest OCTET STRING
}

Which is why the encryption by the Cipher and Signature are different.

make MessageDigest and Cipher equivalent to java.security.Signature

While your requirement

to implement a method to create a signature using an alternative to java.security.Signature

as a whole is questionable (as mentioned in the comments to your question), your solution can be improved.

In particular there is an error in your myCheckSignature code:

// check if digest1 == digest2
if (digest1 == digest2)
return true;
else
return false;

(digest1 == digest2) checks whether you have the identical array object, not whether you have two arrays with equal contents.

What you really want to do is

if (Arrays.equals(digest1, digest2))
return true;
else
return false;

or more compactly

return Arrays.equals(digest1, digest2);

Arrays is a utility class in the package java.util.


By the way, you do

byte[] message = "hello world".getBytes();

getBytes without explicitly selecting a character encoding may result in different results on different platforms or different Java versions. Something that should not happen in this context!

Should I use MessageDigest to verify a digital signature that signed in C#?

The signature verification on Java-side has to fail as you are using different hashing algorithms on both sides.
In C# you are using SHA1 ('HashAlgorithmName.SHA1') and the Java-part is using SHA512 ('Signature signature = Signature.getInstance("Sha512withRSA");').

The following code is using SHA1 as hash algorithm but you can easily change this (on all codelines :-) to SHA256 or SHA512.
The C#-code verifies the signature with the public key, the same public key (encoded as PEM) is used in the Java-code
for verification only.

Security warning: My example codes are using UNSECURE 512-bit RSA-keys that should not used in production.
There is not proper exception handling and you are using the padding 'RSASignaturePadding.Pkcs1' which should
NOT used anymore as well.

This is the output of my C#-code:

Should I use MessageDigest to verify a digital signature that signed in C#?
signedData: mU2bcCMEhG13xG9sKwhaA//dnw2+rbLkwz2737cNU5kb2EBenJIEJ+bA596XccCVKUKPanxMUFoVw2fl8HhCNw==
The data was verified.

That's the Java-output:

RSA instance: SHA1withRSA
The data was verified.

C#-code:

using System;
using System.Security.Cryptography;
using System.Text;

class RSACSPSample {
static void Main() {

try {
Console.WriteLine("Should I use MessageDigest to verify a digital signature that signed in C#?");
// Create a UnicodeEncoder to convert between byte array and string.
ASCIIEncoding ByteConverter = new ASCIIEncoding();

string message = "this is the important message to sign";

// get private and public key ### SAMPLE and UNSECURE 512 bit RSA keypair
var privateKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent><P>8VCRao0hZmIv5gVGFLqOD/7n6TQKlekA96U1QVzimKM=</P><Q>o1bchWA5ddDd59FED37QcrakoTXNoxRspFtsLDKEp1c=</Q><DP>ugF0VUE7wYNlkFP4VPoHjuTZNbRbhHn5uOmrRxqlvyk=</DP><DQ>XoGggC9Hr8pUyo9DIPAP7X+Ny5TU0Vm87m/TK9Ni+2s=</DQ><InverseQ>YqOHEP8dgCvz5Q8nhpQgdrKfdlmjkNAFxKF7j3pm09I=</InverseQ><D>mCpGy/rxS08e5iXn26LRQvvm5UfyLKMNZWmAGk2QF8cRGFB7dds/SI9wGTC9xyOoF4N2kWzYdLx+dYbR9lqwbQ==</D></RSAKeyValue>";
var publicKey = "<RSAKeyValue><Modulus>mfgthqgvK5P6kP00ojzA68+tGMwjEacduojFSukazKPXrZ8Q5XjzfqgJmDQ3wcWe3hWK92O3z/tmAuN47KA0ZQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

// Create a new instance of the RSACryptoServiceProvider class
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider(512);
RSAalg.PersistKeyInCsp = false;
RSAalg.FromXmlString(privateKey);
RSAParameters rsaParameters = RSAalg.ExportParameters(true);

String signedData = SignData(message, rsaParameters);
Console.WriteLine("signedData: " + signedData);

// verify with xml-public key
RSAalg.FromXmlString(publicKey);
rsaParameters = RSAalg.ExportParameters(false);
bool verifiedData = VerifyData(message, signedData, rsaParameters);

// Verify the data and display the result to the
// console.
if (VerifyData(message, signedData, rsaParameters)) {
Console.WriteLine("The data was verified.");
}
else {
Console.WriteLine("The data does not match the signature.");
}
}
catch(ArgumentNullException) {
Console.WriteLine("The data was not signed or verified");
}
}

static string SignData(string message, RSAParameters privateKey)
{
byte[] signedBytes;
using (var rsa = new RSACryptoServiceProvider())
{
var encoder = new UTF8Encoding();
byte[] originalData = encoder.GetBytes(message);
rsa.ImportParameters(privateKey);
signedBytes = rsa.SignData(originalData, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
rsa.PersistKeyInCsp = false;
}
return Convert.ToBase64String(signedBytes);
}

public static bool VerifyData(string message, string signedData, RSAParameters rsaParameters)
{
byte[] messageBytes;
byte[] signedBytes;
using (var rsa = new RSACryptoServiceProvider())
try
{
var encoder = new UTF8Encoding();
messageBytes = encoder.GetBytes(message);
signedBytes = Convert.FromBase64String(signedData);
rsa.ImportParameters(rsaParameters);
return rsa.VerifyData(messageBytes, new SHA1CryptoServiceProvider(), signedBytes);
}
catch(CryptographicException e) {
Console.WriteLine(e.Message);

return false;
}
}
}

Java-code:

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class MainSha1 {
public static void main(String[] args) throws GeneralSecurityException {
System.out.println("Should I use MessageDigest to verify a digital signature that signed in C#?");
String message = "this is the important message to sign";
// this is a SAMPLE and UNSECURE RSA 512 bit key
String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" +
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJn4LYaoLyuT+pD9NKI8wOvPrRjMIxGn\n" +
"HbqIxUrpGsyj162fEOV4836oCZg0N8HFnt4Vivdjt8/7ZgLjeOygNGUCAwEAAQ==\n" +
"-----END PUBLIC KEY-----";
String signedData = "HS4qvrXpqu97me7yDt9lWXp+QLjKMO8FY4kiUiGhMhi6KmXQXCtmcUWSbg0i+LXv7u5ueRiQNeBnu6UCbPhZLg==";
String rsaInstanceString = "SHA1withRSA";
System.out.println("RSA instance: " + rsaInstanceString);
PublicKey publicKey = getPublicKeyFromString(publicKeyPem);
boolean verifyData = verifyRsa(publicKey, rsaInstanceString, message.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(signedData));
if (verifyData = true) {
System.out.println("The data was verified.");
} else {
System.out.println("The data could NOT get verified.");
}

}

public static PublicKey getPublicKeyFromString(String key) throws GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
publicKeyPEM = publicKeyPEM.replaceAll("[\\r\\n]+", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
return pubKey;
}

public static Boolean verifyRsa(PublicKey publicKey, String rsaInstanceString, byte[] messageByte,
byte[] signatureByte) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
Signature publicSignature = Signature.getInstance(rsaInstanceString);
publicSignature.initVerify(publicKey);
publicSignature.update(messageByte);
return publicSignature.verify(signatureByte);
}
}

How to make MessageDigest SHA-1 and Signature NONEwithRSA equivalent to Signature SHA1withRSA

I was able to solve this by doing the following:

  1. The data to be signed needed to be formatted correctly in a
    DigestInfo DER-encoded byte array. The Signature SHA1withRSA takes
    care of this for you, but if you want to accomplish it in a two-step
    process, you need to create your own DigestInfo. I ended up copying
    a very minimal amount of ASN.1 classes from BouncyCastle into my
    project to accomplish this, despite my desire not to use a third
    party lib.

  2. If you try to use the Cipher API to encrypt the DigestInfo, the
    PKCS1 padding will be random and not appropriate for a digital
    signature. I needed static padding.

  3. The Signature.getInstance("NONEwithRSA", "SunMSCAPI") rejects the
    DER-encoded DigestInfo format, and will return an error if you try
    to sign that data. But, since I ultimately wanted to use the PKCS11
    API to generate the signature, I ended up signing the DER-encoded
    DigestInfo with the PKCS11 C_SignInit and C_Sign functions.

To summarize, what worked for me was:

  1. generate the SHA-1 hash of the data to sign using the Java MessageDigest API
  2. generated a DigestInfo DER-encoded ASN.1 object with the SHA-1 hash and SHA-1 OID embedded in the object.
  3. signed the DigestInfo using the PKCS11 C_Sign function from a third party library.

The following links were most helpful in solving my problem:

Oracle Forums: SHA1withRSA - how to do that in 2 steps?

StackOverflow: Using SHA1 and RSA with java.security.Signature vs. MessageDigest and Cipher

What's the detail in SHA1withRSA?

The digital signature algorithm defined in PCKS#1 v15 makes a RSA encryption on digest algorithm identifier and the digest of the message encoded in ASN.1

signature = 
RSA_Encryption(
ASN.1(DigestAlgorithmIdentifier + SHA1(message) ))

See (RFC2313)

10.1 Signature process

The signature process consists of four steps: message digesting, data
encoding, RSA encryption, and octet-string-to-bit-string conversion.
The input to the signature process shall be an octet string M, the
message; and a signer's private key. The output from the signature
process shall be a bit string S, the signature.

So your rsaDecodeHex contains the algorithm identifier and the SHA1 digest of plainText

SHA1 VS RSA: what's the difference between them?

Fundamentally different.

SHA1 is a hash algorithm, which is a one way function, turning an input of any size into a fixed-length output (160 bit in this case). A cryptographic hash function is one for which it should not be possible to find two inputs giving the same output except by brute force (for instance, with a 128-bit function you should need to try on average 2^64 message to find such a "collision" due to something called the birthday paradox - Google it for more).

In fact for SHA1 this is no longer the case - the algorithm is (in cryptographic terms at least) broken now, with a collision attack described by Xiaoyun Wang et al that beats a classic birthday attack. The SHA2 family is not broken, and a process is underway by NIST to agree on a SHA3 algorithm or family of algorithms.

Edit - Google have now generated and published an actual SHA1 collision.

RSA is an asymmetric encryption algorithm, encrypting an input into an output that can then be decrypted (contrast a hash algorithm which can't be reversed). It uses a different key for encryption (the public one) than for decryption (the private one). This can therefore be used to receive encrypted messages from others - you can publish your public key, but only you with the private key can then decrypt the messages that have been encrypted with it.

If you reverse the keys for RSA, it can be used to generate a digital signature - by encrypting something with your private key, anyone can decrypt it with the public key and, if they are sure the public key belongs to you, then they have confidence that you were the one who encrypted the original. This is normally done in conjunction with a hash function - you hash your input, then encrypt that with your private key, giving a digital signature of a fixed length for your input message.

computing digital signature by using AndroidKeyStore

Regarding your first question:

If, as described in your comment, the message is already hashed (e.g. with SHA256) and only needs to be signed using PKCS#1 v1.5 padding, then this is possible with NONEwithRSA. However, it must be specified in the key properties that no digest is used.

If the signature should also comply with the standard, i.e. a verification with SHA256withRSA should be possible, the digest ID (more precisely, the DER encoding of the DigestInfo value) must be placed in front of the hashed message. The following code (based on the posted code) shows this for SHA256 (tested for Android P / API 28):

// Load keystore
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

// Create key (if not already in keystore)
String alias = "Some Alias";
if (!keyStore.containsAlias(alias)) {

Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, 1);

KeyPairGenerator spec = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
spec.initialize(new KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) // for signing / verifying
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) // use RSASSA-PKCS1-v1_5
.setDigests(KeyProperties.DIGEST_NONE) // apply no digest
.setKeySize(2048)
.setKeyValidityStart(notBefore.getTime())
.setKeyValidityEnd(notAfter.getTime())
.setCertificateSubject(new X500Principal("CN=test"))
.setCertificateSerialNumber(BigInteger.ONE)
.build());

spec.generateKeyPair();
}

// Retrieve key
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey();

// Hash message (hashedMessage corresponds to your message)
MessageDigest digest = MessageDigest.getInstance("SHA-256"); // SHA256 as digest assumed
byte[] message = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
byte[] hashedMessage = digest.digest(message);

// Concatenate ID (in this example for SHA256) and message in this order
byte[] id = new byte[]{0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
byte[] idHashedMessage = new byte[id.length + hashedMessage.length];
System.arraycopy(id, 0, idHashedMessage, 0, id.length);
System.arraycopy(hashedMessage, 0, idHashedMessage, id.length, hashedMessage.length);

// Sign with NONEwithRSA
Signature signing = Signature.getInstance("NONEwithRSA");
signing.initSign(privateKey);
signing.update(idHashedMessage);
byte[] signature = signing.sign();

// Verify with SHA256withRSA
Signature verifying = Signature.getInstance("SHA256withRSA"); // Apply algorithm that corresponds to the digest used, here SHA256withRSA
verifying.initVerify(publicKey);
verifying.update(message);
boolean verified = verifying.verify(signature);
System.out.println("Verification: " + verified);

Adding the digest ID is necessary because according to RFC 8017, signing with PKCS#1 v1.5 uses RSASSA-PKCS1-v1_5 padding, which includes the digest ID.


Concerning your second question:

The simple formula "signing equals encryption with the private key" is only valid if no padding is used (textbook RSA), s. also here. In practice, however, padding must always be applied for security reasons. For encryption and signing different paddings are involved: For encryption with PKCS#1 v1.5 padding the variant RSAES-PKCS1-v1_5 is applied and for signing with PKCS#1 v1.5 padding the variant RSASSA-PKCS1-v1_5.

When using private key encryption, the padding variant applied can vary depending on the library (if private key encryption is supported at all), which generally leads to incompatibilities. Probably to avoid such problems, the Android keystore may not support private key encryption (at least I haven't found a configuration that makes this possible).

The Java API as well as the Android API without keystore both support private key encryption. Therefore the last posted code works. Furthermore, for PKCS#1 v1.5 padding the variant RSASSA-PKCS1-v1_5 is used. If here the (e.g. with SHA256) hashed message would be passed and the ID of the digest would be placed in front of it, the generated signature could be verified with the algorithm SHA256withRSA (as in the posted code above).

How to make “MessageDigest SHA-256 and Signature RSASSA-PSS” equivalent to “Signature SHA256withRSA/PSS ”

Don't do the DigestInfo. RSASSA-PKCS1v1_5 signatures use the step of encoding the hash in an ASN.1 DER DigestInfo, but RSASSA-PSS signatures do not; see RFC 3447 or 8017. Also you don't need to specify the parameters on the version using the combined algorithm, because the defaults are already correct, although it doesn't hurt to do so redundantly. Example modified to use my keypair, and output to console:

    KeyStore ks = KeyStore.getInstance("jks"); ks.load(new FileInputStream(args[0]), args[1].toCharArray());
PrivateKey prv = (PrivateKey)ks.getKey(args[2], args[1].toCharArray());
PublicKey pub = ks.getCertificate(args[2]).getPublicKey();

byte[] document = {0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1};
MessageDigest digestor256 = MessageDigest.getInstance("SHA256", "BC");
byte[] documentDigest256 = digestor256.digest(document);

Signature s2 = Signature.getInstance("NONEwithRSASSA-PSS", "BC");
MGF1ParameterSpec mgfParam = new MGF1ParameterSpec("SHA256");
PSSParameterSpec pssParam = new PSSParameterSpec("SHA256", "MGF1", mgfParam, 32, 1);
s2.setParameter(pssParam);
s2.initSign(prv);
s2.update(documentDigest256);
byte[] signature = s2.sign();

Signature ver = Signature.getInstance("SHA256withRSA/PSS", "BC");
if(false){ ver.setParameter(pssParam); } // can enable if desired
ver.initVerify(pub);
ver.update(document);
System.out.println( ver.verify(signature) );

Also, trivially, you had misspelled DefaultDigestAlgorithmIdentifierFinder and used s2 vs s for the variable name.

How Hash Algorithm is determined in Digital Signature?

  1. So, which Hashing Algorithm does Alice uses?

Any strong hashing algorithm, i.e. one which boasts:

  • Pre-image resistance
  • 2nd pre-image resistance
  • Collision resistance
  • is deterministic
  • is fast
  • is uniform in output

  1. Does she uses SHA3-512,SHA3-384, MD5 etc.How was the Hashing algorithm determined in the first place?

SHA1, MD5 are weak, as they don't satisfy the properties mentioned in point 1. Any other algorithm is fine. Some particular hash functions have design that may be beneficial, sponge functions etc.


  1. Can someone provide the right document for it? or an Excerpt from any document.

The choice of which digest to use varies from protocol to protocol, I don't think many of these protocols think further than selecting a digest that's known to be strong.



Related Topics



Leave a reply



Submit