分两步创建 SHA256withRSA

Posted

技术标签:

【中文标题】分两步创建 SHA256withRSA【英文标题】:Create SHA256withRSA in two steps 【发布时间】:2021-12-13 09:59:00 【问题描述】:

出于教育目的,我希望能够首先为字符串创建哈希,然后从该哈希创建 RSA 数字签名,以便结果与一次性使用 SHA256withRSA 时相同。通过这种方式,我想确认我完全理解当我们调用 SHA256withRSA 时实际上为我们自动完成的所有步骤。

我还有一个问题。数字签名是在 Hash 上还是在 Base64 Encoded Hash 上完成的?

下面是我当前使用的代码,这里是代码的输出,显示这两种方法产生不同的签名,这意味着我缺少 soma 手动步骤。

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;

  


【问题讨论】:

对数据的哈希进行签名。您对代码有什么疑问? 所以我必须签署 hashBytes 而不是 hashEncoded?这就是我目前正在做的事情。我已经更新了问题。代码输出显示这两种方法产生不同的签名,这意味着我缺少 soma 手动步骤。 所以我必须签署 hashBytes 而不是 hashEncoded? 是的。关于不同的结果,请看我的回答。 【参考方案1】:

SHA256withRSANoneWithRSA 使用 PKCS#1 v1.5 填充,更准确地说是 RSASSA-PKCS1-v1_5。这是一个确定性填充,即使用相同数据重复签名将产生相同的签名。详情可见RFC8017, 8.2.

当字节序列 (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 时,此填充应用 SHA256 的 DigestInfo 的 DER 编码04 20 被添加到哈希之前。

因此必须修改您的手动代码,例如如下:

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();

    ...

通过此更改,sign() 两个方法返回相同的结果。


顺便说一句,java.util.Base64.Encoder.encodeToString()直接生成一个 Base64 编码的字符串。

此外,在编码/解码时,应始终指定字符集,例如dataString.getBytes(StandardCharsets.UTF_8)new String(..., StandardCharsets.UTF_8)。否则将使用平台的默认字符集,这可能与预期的不同。

【讨论】:

你也可以为 SHA-1 做吗? 对于 SHA-1 类似,只需使用适当的标识符("SHA1withRSA""SHA-1")和适当的字节序列((0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14),有关后者和其他摘要,请参阅 RFC8017, p.47。注意:SHA-1 不安全!【参考方案2】:

这是基于 SHA-1 和 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;

  



结果

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

【讨论】:

以上是关于分两步创建 SHA256withRSA的主要内容,如果未能解决你的问题,请参考以下文章

AIX中如何基于sha256算法为数据文件创建Hashvalue

golang 使用HMAC SHA256创建base64哈希的示例

使用SHA-256和RSA 2048进行加密和签名

适用于iPhone的Objective-C中的Sha256

[Python中具有SHA256的base64 HMAC

sha256可以解密吗