Create SHA256withRSA in two steps
Asked Answered
B

3

13

For educational purposes, I would like to be able to first create a Hash for a String and then to create RSA Digital Signature from that Hash so that the result is the same as when using SHA256withRSA in one go. This way I want to confirm that I fully understand all the steps that are actually being done automatically for us when we call SHA256withRSA.

I also have one additional question. Is Digital Signature done on Hash or on Base64 Encoded Hash?

Below is the code that I am currently using and here is the output of the code which shows that these two approaches produce different signatures which means that I am missing soma manual steps.

AUTOMATICALLY SIGN & VERIFY
SIGNATURE = Hj6a86sR2cJoQFolbxj0blk2I9CAdTdx6WOles5t/pyUyJwa9rp2/SRx2wyXWgc6GsvoZYGLUedeJ2Lklm5hYgT/TtNBATk5eChgfkJMz3NBIRPrsl7ZPG7Wvo4VmHsPpoZZ8PdRk8qY9RLou86OyIqRcX62isuV+e/0deHJ+yTZz4vqA3y+PE4yRFp96A8sKw5VlDnByn7bsxM/QOS+sQWTsETzU9s4YSRfKNq1Urn8/VDoel7n0ORjR918P+0kwE+G77bAOI70yQZorvmbgrMLSBJeVzkKzM/YECLWyrJsqdjfp86FkA9MPGB1V6rO8q8m5GhNoJOmNhC7Ek95Bw==

MANUALLY SIGN & VERIFY
HASH      = lDlahWCWx2b5TYUji52uPeReSW7vbro2wXuRsPKmOdA=
SIGNATURE = gsxw7UQpqni5HyPAw8wI2pvepbrDzizkOvO0hab1+7vi4EaYJi3n4lvnkBTOU5LXQKLZGzJcug0mL2pL/PVh8lrvzZ/F9CxULLxKpayrNkvL9yEWMvcfcku9Go5EGrxSzD7VYvkwOzHvGe4GgUGD1JOjvzXBAfJRT8h/wnZi9IPA9n31/tWI2eFw17Js/gymElycp7pjrpEhUNe/IVTP9HVfRQfAxEDAPW8GY/WFdxbD3Jk05LKvpTxua4jzCX9wJh/s8aiT9OvEXh3/zt06JSEpfgf+CpkOFJupmRhsgqebPfVQEo24ctw1DnipKkL771mm30bFcm/FF1reXuOqqQ==

public class Main {

  //====================================================================================
  // MAIN
  //====================================================================================
  public static void main(String[] args) throws Exception {

    //CREATE DATA
    String            dataString = "Data to be encrypted";
    byte[]            dataBytes  = dataString.getBytes();

    //CREATE KEYS
    KeyPairGenerator  keyPairGenerator = KeyPairGenerator.getInstance("RSA");
                      keyPairGenerator.initialize(2048);
    KeyPair           keyPair    = keyPairGenerator.generateKeyPair();
    PrivateKey        privateKey = keyPair.getPrivate();

    //AUTOMATICALLY SIGN & VERIFY
    System.out.println("\nAUTOMATICALLY SIGN & VERIFY");
    byte[]  automaticSignatureBytes = AutomaticHash.sign  ("SHA256withRSA", privateKey, dataBytes);

    //MANUALLY SIGN & VERIFY
    System.out.println("\nMANUALLY SIGN & VERIFY");
    byte[]  manualHashBytes         = ManualHash.hash  ("SHA-256", dataBytes);
    byte[]  manualSignatureBytes    = ManualHash.sign  ("NONEwithRSA", privateKey, manualHashBytes);

  }

}

public class AutomaticHash {

  //====================================================================================
  // AUTOMATICALLY SIGN
  //====================================================================================
  public static byte[] sign(String algorithms, PrivateKey privateKey, byte[] dataBytes) throws Exception {

    //CREATE SIGNATURE (use Hash first)
    Signature         signature = Signature.getInstance(algorithms);
                      signature.initSign(privateKey);
                      signature.update(dataBytes);
    byte[]            signatureBytes = signature.sign();

    //ENCODE SIGNATURE
    byte[]            signatureEncodedBytes  = Base64.getEncoder().encode(signatureBytes);
    String            signatureEncodedString = new String(signatureEncodedBytes);

    //DISPLAY ENCODED SIGNATURE
    System.out.println("SIGNATURE = " + signatureEncodedString);

    //RETURN SIGNATURE
    return signatureBytes;

  }

}

public class ManualHash {

  //====================================================================================
  // MANUALLY HASH
  //====================================================================================
  public static byte[] hash(String algorithm, byte[] dataBytes) throws Exception {

    //CREATE HASH
    MessageDigest digest    = MessageDigest.getInstance(algorithm);
    byte[]        hashBytes = digest.digest(dataBytes);

    //ENCODE HASH
    byte[]        hashEncoded = Base64.getEncoder().encode(hashBytes);
    String        hashEncodedString = new String(hashEncoded);

    //DISPLAY ENCODED HASH
    System.out.println("HASH      = " + hashEncodedString);

    //RETURN HASH
    return hashBytes;

  }

  //====================================================================================
  // MANUALLY SIGN
  //====================================================================================
  public static byte[] sign(String algorithm, PrivateKey privateKey, byte[] hashBytes) throws Exception {

    //SIGN HASH
    Signature         signature = Signature.getInstance(algorithm);
                      signature.initSign(privateKey);
                      signature.update(hashBytes);
    byte[]            signatureBytes = signature.sign();

    //ENCODE SIGNATURE
    byte[]            signatureEncodedBytes = Base64.getEncoder().encode(signatureBytes);
    String            signatureEncodedString = new String(signatureEncodedBytes);

    //DISPLAY ENCODED HASH & SIGNATURE
    System.out.println("SIGNATURE = " + signatureEncodedString);

    //RETURN SIGNATURE
    return signatureBytes;

  }

}
Bags answered 28/10, 2021 at 7:39 Comment(3)
The hash of the data is signed. What is your question regarding the code?Athena
So I have to sign hashBytes and not hashEncoded? Which is what I am currently doing. I have updated question. Output of the code shows that these two approaches produce different signatures which means that I am missing soma manual steps.Bags
So I have to sign hashBytes and not hashEncoded? Yes. Regarding the different results, please see my answer.Athena
A
7

SHA256withRSA and NoneWithRSA use PKCS#1 v1.5 padding, more precisely RSASSA-PKCS1-v1_5. This is a deterministic padding, i.e. repeated signing with the same data will produce the same signature. Details can be found in RFC8017, 8.2.

This padding applies the DER encoding of the DigestInfo that results for SHA256 when the byte sequence (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 is prepended to the hash.

So your manual code must be modified e.g. as follows:

public static byte[] sign(String algorithm, PrivateKey privateKey, byte[] hashBytes) throws Exception {

    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[] derDigestInfo = new byte[id.length + hashBytes.length];
    System.arraycopy(id, 0, derDigestInfo, 0, id.length);
    System.arraycopy(hashBytes, 0, derDigestInfo, id.length, hashBytes.length);

    // SIGN HASH
    Signature signature = Signature.getInstance(algorithm);
    signature.initSign(privateKey);
    signature.update(derDigestInfo);
    byte[] signatureBytes = signature.sign();

    ...

With this change, both sign() methods return the same result.


Btw, java.util.Base64.Encoder.encodeToString() directly generates a Base64 encoded string.

In addition, when encoding/decoding, a charset should always be specified, e.g. dataString.getBytes(StandardCharsets.UTF_8) or new String(..., StandardCharsets.UTF_8). Otherwise the platform's default charset is used, which may differ from the intended one.

Athena answered 28/10, 2021 at 8:21 Comment(2)
Can you also do it for SHA-1?Bags
For SHA-1 it is analogous, just use the appropriate identifiers ("SHA1withRSA" and "SHA-1") and the appropriate byte sequence ((0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14), see for the latter and other digests RFC8017, p.47. Caution: SHA-1 is insecure!Athena
B
5

Here is a complete code based on Comments for SHA-1 and SHA-256

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;

public class Main {

  //====================================================================================
  // MAIN
  //====================================================================================
  public static void main(String[] args) throws Exception {

    //CREATE DATA
    String            dataString = "Data to be encrypted";
    byte[]            dataBytes  = dataString.getBytes(StandardCharsets.UTF_8);

    //CREATE KEYS
    KeyPairGenerator  keyPairGenerator = KeyPairGenerator.getInstance("RSA");
                      keyPairGenerator.initialize(2048);
    KeyPair           keyPair    = keyPairGenerator.generateKeyPair();
    PrivateKey        privateKey = keyPair.getPrivate();
    PublicKey         publicKey  = keyPair.getPublic();

    //SIGN
    SHA1withRSA  (privateKey, publicKey, dataBytes);
    SHA256withRSA(privateKey, publicKey, dataBytes);

  }

  //====================================================================================
  // SHA1 WITH RSA
  //====================================================================================
  private static void SHA1withRSA(PrivateKey privateKey, PublicKey publicKey, byte[] dataBytes) throws Exception {

    //LOG
    System.out.println("\nSHA1 WITH RSA ==================================================================");

    //AUTOMATICALLY SIGN & VERIFY
    System.out.println("\nAUTOMATICALLY SIGN & VERIFY");
    byte[]  automaticSignatureBytes = AutomaticHash.sign  ("SHA1withRSA", privateKey, dataBytes);
    Boolean automaticVerified       = AutomaticHash.verify("SHA1withRSA", publicKey , dataBytes, automaticSignatureBytes);

    //MANUALLY SIGN & VERIFY
    System.out.println("\nMANUALLY SIGN & VERIFY");
    byte[]  manualHashBytes         = ManualHash.hash   ("SHA-1", dataBytes);
    byte[]  manualPaddingHashBytes  = ManualHash.padding("SHA-1", manualHashBytes);
    byte[]  manualSignatureBytes    = ManualHash.sign   ("NONEwithRSA", privateKey, manualPaddingHashBytes);
    Boolean manualVerified          = ManualHash.verify ("NONEwithRSA", publicKey , manualPaddingHashBytes, manualSignatureBytes);

  }

  //====================================================================================
  // SHA256 WITH RSA
  //====================================================================================
  private static void SHA256withRSA(PrivateKey privateKey, PublicKey publicKey, byte[] dataBytes) throws Exception {

    //LOG
    System.out.println("\nSHA256 WITH RSA ================================================================");

    //AUTOMATICALLY SIGN & VERIFY
    System.out.println("\nAUTOMATICALLY SIGN & VERIFY");
    byte[]  automaticSignatureBytes = AutomaticHash.sign  ("SHA256withRSA", privateKey, dataBytes);
    Boolean automaticVerified       = AutomaticHash.verify("SHA256withRSA", publicKey , dataBytes, automaticSignatureBytes);

    //MANUALLY SIGN & VERIFY
    System.out.println("\nMANUALLY SIGN & VERIFY");
    byte[]  manualHashBytes         = ManualHash.hash   ("SHA-256", dataBytes);
    byte[]  manualPaddingHashBytes  = ManualHash.padding("SHA-256", manualHashBytes);
    byte[]  manualSignatureBytes    = ManualHash.sign   ("NONEwithRSA", privateKey, manualPaddingHashBytes);
    Boolean manualVerified          = ManualHash.verify ("NONEwithRSA", publicKey , manualPaddingHashBytes, manualSignatureBytes);

  }

}

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;

public class AutomaticHash {

  //====================================================================================
  // AUTOMATICALLY SIGN
  //====================================================================================
  public static byte[] sign(String algorithms, PrivateKey privateKey, byte[] dataBytes) throws Exception {

    //CREATE SIGNATURE (use Hash first)
    Signature         signature = Signature.getInstance(algorithms);
                      signature.initSign(privateKey);
                      signature.update(dataBytes);
    byte[]            signatureBytes = signature.sign();

    //DISPLAY ENCODED SIGNATURE
    System.out.println("SIGNATURE = " + Base64.getEncoder().encodeToString(signatureBytes));

    //RETURN SIGNATURE
    return signatureBytes;

  }

  //====================================================================================
  // AUTOMATICALLY VERIFY
  //====================================================================================
  public static Boolean verify(String algorithms, PublicKey publicKey, byte[] dataBytes, byte[] signatureBytes) throws Exception {

    //INITIALIZE SIGNATURE
    Signature signature = Signature.getInstance(algorithms);
              signature.initVerify(publicKey);
              signature.update(dataBytes);

    //VERIFY SIGNATURE
    boolean   verified = signature.verify(signatureBytes);

    //DISPLAY VERIFICATION
    System.out.println("VERIFIED  = " + verified);

    //RETURN SIGNATURE
    return verified;

  }

}

import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;

public class ManualHash {

  //====================================================================================
  // HASH
  //====================================================================================
  public static byte[] hash(String algorithm, byte[] dataBytes) throws Exception {

    //CREATE HASH
    MessageDigest digest    = MessageDigest.getInstance(algorithm);
    byte[]        hashBytes = digest.digest(dataBytes);

    //DISPLAY ENCODED HASH
    System.out.println("HASH      = " + Base64.getEncoder().encodeToString(hashBytes));

    //RETURN HASH
    return hashBytes;

  }

  //====================================================================================
  // PADDING
  //====================================================================================
  public static byte[] padding(String algorithm, byte[] hashBytes) throws Exception {

    //PREPARE PADDING
    byte[] padding = null;
    if (algorithm.equals("SHA-1"  )) { padding = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, (byte) 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14                         }; }
    if (algorithm.equals("SHA-256")) { padding = new byte[] { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 }; }

    //ADD PADDING & HASH TO RESULTING ARRAY
    byte[] paddingHash = new byte[padding.length + hashBytes.length];
    System.arraycopy(padding  , 0, paddingHash, 0             , padding.length  );
    System.arraycopy(hashBytes, 0, paddingHash, padding.length, hashBytes.length);

    //RETURN HASH
    return paddingHash;

  }

  //====================================================================================
  // SIGN
  //====================================================================================
  public static byte[] sign(String algorithm, PrivateKey privateKey, byte[] paddingHash) throws Exception {

    //SIGN PADDED HASH
    Signature         signature = Signature.getInstance(algorithm);
                      signature.initSign(privateKey);
                      signature.update(paddingHash);
    byte[]            signatureBytes = signature.sign();

    //DISPLAY ENCODED HASH & SIGNATURE
    System.out.println("SIGNATURE = " + Base64.getEncoder().encodeToString(signatureBytes));

    //RETURN SIGNATURE
    return signatureBytes;

  }

  //====================================================================================
  // MANUALLY VERIFY
  //====================================================================================
  public static Boolean verify(String algorithm, PublicKey publicKey, byte[] hashBytes, byte[] signatureBytes) throws Exception {

    //INITIALIZE SIGNATURE
    Signature signature = Signature.getInstance(algorithm);
              signature.initVerify(publicKey);
              signature.update(hashBytes);

    //VERIFY SIGNATURE
    boolean   verified = signature.verify(signatureBytes);

    //DISPLAY VERIFICATION
    System.out.println("VERIFIED  = " + verified);

    //RETURN SIGNATURE
    return verified;

  }

}

Result

SHA1 WITH RSA ==================================================================

AUTOMATICALLY SIGN & VERIFY
SIGNATURE = bGLX8elMdZeyDmT1YCh86Uz5kH9P69NpAqYYNn11ybyYp2cqLUSyqTEolhv4QgEAnSoyGQH9McjEMqhDL+u8Kp68nDRmt2ScFtNN17i0F7+65XO2PHqQtpfOsb2tUoG4Q+SmoBWSiXJliIQTRjYeblgLteY74SJreDlN7AAEYmFpi9oVbptwCj0OnZMlz5EdffX66Yy47qxYSF3O3Wuea1mCY/lG52qHwTFFJ+//N+X/5wFX4UDpzA+BMoWynE7FKzzfkQOGl9LazhFCTH46i30UHXmNGlbrjpApRtZKL9LnZUNPbLJTVKtCDZFMEdocT/a9NBeclZPtu/LBj3l+Iw==
VERIFIED  = true

MANUALLY SIGN & VERIFY
HASH      = +AayjWUd2pEmeoC49PlJj2mMdG4=
SIGNATURE = bGLX8elMdZeyDmT1YCh86Uz5kH9P69NpAqYYNn11ybyYp2cqLUSyqTEolhv4QgEAnSoyGQH9McjEMqhDL+u8Kp68nDRmt2ScFtNN17i0F7+65XO2PHqQtpfOsb2tUoG4Q+SmoBWSiXJliIQTRjYeblgLteY74SJreDlN7AAEYmFpi9oVbptwCj0OnZMlz5EdffX66Yy47qxYSF3O3Wuea1mCY/lG52qHwTFFJ+//N+X/5wFX4UDpzA+BMoWynE7FKzzfkQOGl9LazhFCTH46i30UHXmNGlbrjpApRtZKL9LnZUNPbLJTVKtCDZFMEdocT/a9NBeclZPtu/LBj3l+Iw==
VERIFIED  = true

SHA256 WITH RSA ================================================================

AUTOMATICALLY SIGN & VERIFY
SIGNATURE = M7bqVsCdlQnjCBBupuOmCJSzhuZ4C2K1VsNcJUXpdKBt17jy3TbFKcaYQ47Lsj0x/xqHFK3OTFxxZRjZyywqk25ovyzTfCkwHoEYWE2TiApEkf92pTcVn86YkFhDQCSJWboJu75NAEtDxkiJelUut3DmHp0Pu91U17cKC6JxUSIHlTQAxbNHhUfkx0YdE6sTvzK2lG/XJcZdA/Nx6qia9548EsBoi71vY1GlsSn7pEf5pg5yCJF5fAufD9hRSPI5OX2TKtj3hYXMr8UXQ6VaDBf4PiJjQ8MNg+q4cXtr+BpK1uHtxm0BuRHlcM4eBkTyuia93Xc88Ojwsma2tlCrwg==
VERIFIED  = true

MANUALLY SIGN & VERIFY
HASH      = lDlahWCWx2b5TYUji52uPeReSW7vbro2wXuRsPKmOdA=
SIGNATURE = M7bqVsCdlQnjCBBupuOmCJSzhuZ4C2K1VsNcJUXpdKBt17jy3TbFKcaYQ47Lsj0x/xqHFK3OTFxxZRjZyywqk25ovyzTfCkwHoEYWE2TiApEkf92pTcVn86YkFhDQCSJWboJu75NAEtDxkiJelUut3DmHp0Pu91U17cKC6JxUSIHlTQAxbNHhUfkx0YdE6sTvzK2lG/XJcZdA/Nx6qia9548EsBoi71vY1GlsSn7pEf5pg5yCJF5fAufD9hRSPI5OX2TKtj3hYXMr8UXQ6VaDBf4PiJjQ8MNg+q4cXtr+BpK1uHtxm0BuRHlcM4eBkTyuia93Xc88Ojwsma2tlCrwg==
VERIFIED  = true
Bags answered 28/10, 2021 at 10:32 Comment(0)
H
0

Solution with bouncy castle can look like this

@Test
fun signDigestAndMessageSHA256() {
    val message = "message"

    val digest = MessageDigest.getInstance("SHA-256").digest(payload.toByteArray())
    val encodedDigest: ByteArray = DigestInfo(AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE), digest).encoded

    var signature = Signature.getInstance("NONEwithRSA")
    signature.initSign(privateKey)
    signature.update(encodedDigest)
    val signedDigest = signature.sign()

    signature = Signature.getInstance("SHA256withRSA")
    signature.initSign(privateKey)
    signature.update(payload.toByteArray())
    val signedMessage = signature.sign()

    Assert.assertEquals(signedDigest.joinToString(), signedMessage.joinToString())
}
Hundredfold answered 24/1, 2023 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.