【问题标题】:How to verify if the private key matches with the certificate..?如何验证私钥是否与证书匹配..?
【发布时间】:2021-11-06 10:14:11
【问题描述】:

我将私钥存储为 .key 文件..

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQD5YBS6V3APdgqaWAkijIUHRK4KQ6eChSaRWaw9L/4u8o3T1s8J
rUFHQhcIo5LPaQ4BrIuzHS8yzZf0m3viCTdZAiDn1ZjC2koquJ53rfDzqYxZFrId
7a4QYUCvM0gqx5nQ+lw1KoY/CDAoZN+sO7IJ4WkMg5XbgTWlSLBeBg0gMwIDAQAB
AoGASKDKCKdUlLwtRFxldLF2QPKouYaQr7u1ytlSB5QFtIih89N5Avl5rJY7/SEe
rdeL48LsAON8DpDAM9Zg0ykZ+/gsYI/C8b5Ch3QVgU9m50j9q8pVT04EOCYmsFi0
DBnwNBRLDESvm1p6NqKEc7zO9zjABgBvwL+loEVa1JFcp5ECQQD9/sekGTzzvKa5
SSVQOZmbwttPBjD44KRKi6LC7rQahM1PDqmCwPFgMVpRZL6dViBzYyWeWxN08Fuv
p+sIwwLrAkEA+1f3VnSgIduzF9McMfZoNIkkZongcDAzjQ8sIHXwwTklkZcCqn69
qTVPmhyEDA/dJeAK3GhalcSqOFRFEC812QJAXStgQCmh2iaRYdYbAdqfJivMFqjG
vgRpP48JHUhCeJfOV/mg5H2yDP8Nil3SLhSxwqHT4sq10Gd6umx2IrimEQJAFNA1
ACjKNeOOkhN+SzjfajJNHFyghEnJiw3NlqaNmEKWNNcvdlTmecObYuSnnqQVqRRD
cfsGPU661c1MpslyCQJBAPqN0VXRMwfU29a3Ve0TF4Aiu1iq88aIPHsT3GKVURpO
XNatMFINBW8ywN5euu8oYaeeKdrVSMW415a5+XEzEBY=
-----END RSA PRIVATE KEY-----

我从 ssl 证书文件中提取了公钥..

以下是我尝试验证私钥是否与 ssl 证书匹配的代码..

我使用了模数[即私钥获取模数==公钥获取模数]来检查它们是否匹配..

这似乎只适用于 RSAKEYS..

但我也想检查其他键..

还有其他替代方法可以做同样的事情吗??

  private static boolean verifySignature(File serverCertificateFile, File serverCertificateKey) {
    try {
        byte[] certificateBytes = FileUtils.readFileToByteArray(serverCertificateFile);
        //byte[] keyBytes = FileUtils.readFileToByteArray(serverCertificateKey);
        RandomAccessFile raf = new RandomAccessFile(serverCertificateKey, "r");
        byte[] buf = new byte[(int) raf.length()];
        raf.readFully(buf);
        raf.close();
        PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(buf);
        KeyFactory kf;
        try {
            kf = KeyFactory.getInstance("RSA");

            RSAPrivateKey privKey = (RSAPrivateKey) kf.generatePrivate(kspec);


            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
            InputStream in = new ByteArrayInputStream(certificateBytes);
            //Generate Certificate in X509 Format
            X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
            RSAPublicKey publicKey = (RSAPublicKey) cert.getPublicKey();

            in.close();
            return privKey.getModulus() == publicKey.getModulus();

        } catch (NoSuchAlgorithmException ex) {
            logger.log(Level.SEVERE, "Such algorithm is not found", ex);
        }  catch (CertificateException ex) {
            logger.log(Level.SEVERE, "certificate exception", ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(CertificateConversion.class.getName()).log(Level.SEVERE, null, ex);
        }

    } catch (IOException ex) {
        logger.log(Level.SEVERE, "Signature verification failed.. This could be because the file is in use", ex);
    }
    return false;
}

而且代码也不起作用.. 抛出 invalidkeyspec 异常

【问题讨论】:

  • "这似乎只适用于 RSAKEYS.. 但我也想检查其他密钥.." # 还有哪些其他类型的密钥?
  • 这里提到了一个非常好的解决方案:stackoverflow.com/a/23277782/4120181

标签: java ssl cryptography bouncycastle


【解决方案1】:

此解决方案适用于我的情况,用于初步检查椭圆曲线键是否匹配:

boolean matches(PrivateKey privateKey, PublicKey publicKey) {
    return ((ECKey) privateKey).getParams().equals(((ECKey) publicKey).getParams());
}

另请参阅@SteffenHeil 的答案,了解更多数学检查,这些检查对我来说结果相同。

有趣的是,Apple Pay 颁发的商户支付处理证书与父级“Apple Worldwide Developer Relations CA - G2”证书具有完全相同的((ECKey) publicKey).getParams()。 所以它们都匹配私钥,并且叶子只能通过 getSubjectDN() = getIssuerDN() 检查来解决。

【讨论】:

  • java.lang.ClassCastException: sun.security.rsa.RSAPrivateKeyImpl 不能转换为 java.security.interfaces.ECKey
  • @deFreitas,请参阅有关 RSA 案例的其他答案。这仅在 EC 案例中补充它们。
【解决方案2】:

如果要检查一个 RSA publicKey 和一个 RSA privateKey 是否属于一起,可以使用以下代码:

RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
return rsaPublicKey.getModulus().equals( rsaPrivateKey.getModulus() )
    && BigInteger.valueOf( 2 ).modPow( rsaPublicKey.getPublicExponent()
    .multiply( rsaPrivateKey.getPrivateExponent() ).subtract( BigInteger.ONE ),
    rsaPublicKey.getModulus() ).equals( BigInteger.ONE );

我正在为 EC 寻找类似的解决方案...


到目前为止,我们已经找到了 ECC 的方法: 然而,它有点复杂:

ECPublicKey pk = (ECPublicKey) publicKey;
ECPrivateKey sk = (ECPrivateKey) privateKey;
ECParameterSpec pkSpec = pk.getParams(), skSpec = sk.getParams();
EllipticCurve skCurve = skSpec.getCurve(), pkCurve = pkSpec.getCurve();
ECField skField = skCurve.getField(), pkField = pkCurve.getField();
BigInteger skA = skCurve.getA(), skB = skCurve.getB();
if ( pkSpec != skSpec //
  && ( pkSpec.getCofactor() != skSpec.getCofactor() //
    || ! pkSpec.getOrder().equals( skSpec.getOrder() ) //
    || ! pkSpec.getGenerator().equals( skSpec.getGenerator() ) //
    || pkCurve != skCurve //
    && ( ! pkCurve.getA().equals( skA ) //
      || ! pkCurve.getB().equals( skB ) //
      || skField.getFieldSize() != pkField.getFieldSize() ) ) ) //
  return false;
ECPoint w = pk.getW();
BigInteger x = w.getAffineX(), y = w.getAffineY();
if ( skField instanceof ECFieldFp ) {
  BigInteger skP = ( (ECFieldFp) skField ).getP();
  return pkField instanceof ECFieldFp && skP.equals( ( (ECFieldFp) pkField ).getP() ) //
    && y.pow( 2 ).subtract( x.pow( 3 ) ).subtract( skA.multiply( x ) ).subtract( skB ).mod( skP ).signum() == 0;
}
if ( skField instanceof ECFieldF2m ) {
  int m = ( (ECFieldF2m) skField ).getM();
  BigInteger rp = ( (ECFieldF2m) skField ).getReductionPolynomial();
  if ( ! ( pkField instanceof ECFieldF2m ) || m != ( (ECFieldF2m) skField ).getM() || ! rp.equals( ( (ECFieldF2m) skField ).getReductionPolynomial() ) )
    return false;
  BigInteger x2 = f2mReduce( f2mMultiply( x, x ), rp, m );
  return f2mReduce( f2mSum( f2mMultiply( y, y ), f2mMultiply( x, y ), f2mMultiply( x, x2 ), f2mMultiply( skA, x2 ), skB ), rp, m ).signum() == 0;
}

这里是数学辅助函数:

public static final BigInteger f2mSum( BigInteger ... values )
{
  if ( values.length == 0 )
    return BigInteger.ZERO;
  BigInteger result = values[ 0 ];
  for ( int i = values.length - 1; i > 0; i -- )
    result = result.xor( values[ i ] );
  return result;
}


public static final BigInteger f2mAdd( BigInteger a, BigInteger b )
{
  return a.xor( b );
}


public static final BigInteger f2mSubtract( BigInteger a, BigInteger b )
{
  return a.xor( b );
}


public static final BigInteger f2mMultiply( BigInteger a, BigInteger b )
{
  BigInteger result = BigInteger.ZERO, sparse, full;
  if ( a.bitCount() > b.bitCount() ) {
    sparse = b;
    full = a;
  } else {
    sparse = b;
    full = a;
  }
  for ( int i = sparse.bitLength(); i >= 0; i -- )
    if ( sparse.testBit( i ) )
      result = result.xor( full.shiftLeft( i ) );
  return result;
}


public static final BigInteger f2mReduce( BigInteger input, BigInteger reductionPolynom, int bitLength )
{
  while ( input.bitLength() > bitLength )
    input = input.xor( reductionPolynom.shiftLeft( input.bitLength() - reductionPolynom.bitLength() ) );
  return input;
}

【讨论】:

  • 你现在找到EC的解决方案了吗?
  • 感谢 Steffen 的 EC 案例。但我注意到f2mAddf2mSubtract 未使用,f2mMultiplyif ( a.bitCount() > b.bitCount() ) 有相同的分支,这似乎是一个错误。能否请您也参考一下算法的来源?
【解决方案3】:

您的代码很好,只需添加以下内容:

    String file = "qwerty";
    byte[] fileBytes = file.getBytes();
    byte[] digitalSignature = signData(fileBytes, privKey);
    System.out.println("SIGNATURE MADE");
    boolean verified;
    verified = verifySig(fileBytes, publicKey, digitalSignature);
    System.out.println("verified: " + verified) ;

  public static byte[] signData(byte[] data, PrivateKey key) throws Exception {
    Signature signer = Signature.getInstance("SHA256withRSA");
    signer.initSign(key);
    signer.update(data);
    return (signer.sign());
  }

  public static boolean verifySig(byte[] data, PublicKey key, byte[] sig) throws Exception {
    Signature signer = Signature.getInstance("SHA256withRSA");
    signer.initVerify(key);
    signer.update(data);
    return (signer.verify(sig));
  }

【讨论】:

    【解决方案4】:

    那么,成对参数检查有什么问题?

    • 如果证书指定“RSA”类型的公钥,则:
      1. 从密钥文件中提取ne
      2. 将这些值与证书中的值进行比较。
    • 如果证书指定“DSA”类型的公钥,则:
      1. 从密钥文件中提取pqgy
      2. 将这些值与证书中的值进行比较。
    • 如果证书指定类型为“blah-blah”的公钥,则:
      1. 从密钥文件中提取相应的值。
      2. 将这些值与证书中的值进行比较。

    等等,我。 e.每种算法都需要自己正确处理。如果密钥文件格式实际上是自定义的,则可能不存在通用算法。但是,您仍然可以通过仅指定值索引来稍微概括它:

    ComparisonScheme = new Dictionary<String, Integer[2][]> {
        { "RSA", {{0, 0}, {1, 1}} },
        { "DSA", {{0, 1}, {1, 2}, {2, 3}, {3, 0}} },
    }
    

    这只是一个例子,当然,不要认真对待语法和数字。

    【讨论】:

      【解决方案5】:

      使用私钥对某些内容进行签名,并使用证书中的公钥对其进行验证。除非它们是一对,否则这将失败。

      【讨论】:

      • 这只是一个编码问题。与这个答案无关。
      • 签名并检查结果就像通过测试它是否适用于某个变量赋值来验证数学公式。最好根据算法要求检查参数的属性。例如,请参阅我对 RSA 和 ECC 代码的回答。 DSA 与 RSA 一样简单。
      • 如果您担心通过一次测试获得好运。针对不同的值运行测试。如果检查成功,则密钥很可能会成对。
      • @user207421 这只有在 Java 提供者支持该算法时才有效。
      • @SteffenHeil 它只需要保留一个特定的变量。私钥只有一个值。
      【解决方案6】:

      我对此有点生疏,但有一点帮助总比没有好,对吧?

      一般来说,一旦你有了公钥/私钥,你应该能够使用 Cipher 用一个密钥加密少量数据,看看是否可以用另一个密钥成功解密。这是蛮力,可能会影响性能,但可能适合您尝试做的事情(我会提供代码,但我处于无法测试它的环境中,我不想发布任何内容未经测试。Encrypt and decrypt large string in java using RSA 有使用 Cipher 的代码 sn-ps,如果您有兴趣)。

      此外,除非您的示例私钥文件中的标头已被篡改,否则它不是 PKCS8 格式。 PKCS8 文件不会在标头中指定算法(例如 RSA),因为它们可以使用不同的算法,从而指定在编码本身中使用的算法。

      另外,如果我没记错的话,原生 java 对 PEM 的支持很糟糕。我相信您需要从密钥文件中删除 PEM 标头并对其进行 base64 解码,然后才能对其进行任何操作。或者,如果可能,您可以确保您的密钥文件是 DER 编码的,在这种情况下,密钥规范应该可以很好地读取它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-29
        • 2011-10-16
        • 2014-06-09
        • 1970-01-01
        • 2021-11-09
        相关资源
        最近更新 更多