欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OPENSSL: ECDSA算法验签之签名数据的DER格式同一处理

程序员文章站 2022-06-29 18:34:49
...

1、发现问题:

     客户用“SHA256withECDSA”生成的签名,我们用openssl算法的验签接口有些可以通过,有些没法通过。

2、自发验证-方法一:

     通过php的脚本验证这个问题,代码如下:  


//验证签名
$algo = "SHA256";
// 304402205E4799D8E8199184FB7613000781EE5D7AE2E27121733E8B391FD33C51BD80C7022039EAF55B1C3958F39764D64EC18945E1A63AB7DA2DD9D6D1BA9F1BC95FDDCCEB
$signature = "MEQCIF5HmdjoGZGE+3YTAAeB7l164uJxIXM+izkf0zxRvYDHAiA56vVbHDlY85dk1k7BiUXhpjq32i3Z1tG6nxvJX93M6w==";
$pubKeyContent="-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMrpFAuH2QXyMLYE3vlA3CfHDPhyp\nnEMu/FPajy/woq1MSHcwHeu0Ui0PczdzJ5qLcLo6srJjVG8WqgvXn2eryA==\n-----END PUBLIC KEY-----";
$pKey=openssl_get_publickey($pubKeyContent);
// 3930303034303031303032353030303120062317084014952068DB5C0989EEB00F1F128BBC990DF9C1D0CCAFFDEA3973021F945AF8B2589C955EF3181F8CA05B3059301306072A8648CE3D020106082A8648CE3D03010703420004B29046B2F93A2BFCF497390FEF8E1BFE0C34FA89D3A45AF925A22571B1C1B5E7A7409A73A7F8F9C1A61D3F9FF284926819EAEBF2E6D4968A46F112B0811A4E0501A10100
$message="OTAwMDQwMDEwMDI1MDAwMSAGIxcIQBSVIGjbXAmJ7rAPHxKLvJkN+cHQzK/96jlzAh+UWviyWJyVXvMYH4ygWzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLKQRrL5Oiv89Jc5D++OG/4MNPqJ06Ra+SWiJXGxwbXnp0Cac6f4+cGmHT+f8oSSaBnq6/Lm1JaKRvESsIEaTgUBoQEA";

$success = openssl_verify(base64_decode($message), base64_decode($signature), $pKey, $algo);

if ($success === -1) {
	$return = openssl_error_string();
} elseif ($success === 1) {
	$return = "Verification signature success";
} else {
	$return = openssl_error_string();
}

echo "\r\n 1-return : ($success):($return) \r\n";


//验证签名
$algo = "SHA256";
//304402209903A6794AF9E758295E486064FC6ECB63F62E5F04F66579E99A83800D32CB0902203984B7421A2E337388C782E2FF86F23D9EF313BA5D73E598D6BE2F0B99B4FE69
$signature = "MEQCIJkDpnlK+edYKV5IYGT8bstj9i5fBPZleemag4ANMssJAiA5hLdCGi4zc4jHguL/hvI9nvMTul1z5ZjWvi8LmbT+aQ==";
$pubKeyContent="-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMrpFAuH2QXyMLYE3vlA3CfHDPhyp\nnEMu/FPajy/woq1MSHcwHeu0Ui0PczdzJ5qLcLo6srJjVG8WqgvXn2eryA==\n-----END PUBLIC KEY-----";
$pKey=openssl_get_publickey($pubKeyContent);
//3930303034303031303032353030303120062311462329072068DB5C0989EEB00F1F128BBC990DF9C1D0CCAFFDEA3973021F945AF8B2589C955EF2CC8F8CA05B3059301306072A8648CE3D020106082A8648CE3D03010703420004C507CABC29EAA162A33A14D190BA3100204D82E6D3F20E54BCD73A3FD78F657B67BB2E783692CB8D6A75516257633148CBA492DCE19B32410DEE1B62EAE1DAAB01A10100
$message="OTAwMDQwMDEwMDI1MDAwMSAGIxFGIykHIGjbXAmJ7rAPHxKLvJkN+cHQzK/96jlzAh+UWviyWJyVXvLMj4ygWzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMUHyrwp6qFiozoU0ZC6MQAgTYLm0/IOVLzXOj/Xj2V7Z7sueDaSy41qdVFiV2MxSMukktzhmzJBDe4bYurh2qsBoQEA";

$success = openssl_verify(base64_decode($message), base64_decode($signature), $pKey, $algo);

if ($success === -1) {
	$return = openssl_error_string();
} elseif ($success === 1) {
	$return = "Verification signature success";
} else {
	$return = openssl_error_string();
}

echo "\r\n 2-return : ($success):($return) \r\n";

        执行结果如下:

        OPENSSL: ECDSA算法验签之签名数据的DER格式同一处理

       结论:php的验证接口表现和c调用openssl的表现一致,两者都依赖openssl开源库

3、自发验证-方法二: 

           java的ecdsa算法验签接口:

           ECDSAUtil.java

package ecdsa;
 
import javax.crypto.KeyAgreement;
import javax.xml.bind.DatatypeConverter;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
 
/**
 * ECCDSA加签验签工具类
 * @author Administrator
 *
 */
public class ECDSAUtil {
	
	 private static final String SIGNALGORITHMS = "SHA256withECDSA";
	 private static final String ALGORITHM = "EC";
	 private static final String SECP256K1 = "secp256k1";
 

public static void main(final String[] args) throws Exception {
 
   
//        生成公钥私钥
        final KeyPair keyPair1 = getKeyPair();
        final PublicKey publicKey1 = keyPair1.getPublic();
        final PrivateKey privateKey1 = keyPair1.getPrivate();
        //**转16进制字符串
        final String publicKey = HexUtil.encodeHexString(publicKey1.getEncoded());
        final String privateKey = HexUtil.encodeHexString(privateKey1.getEncoded());
        //String privateKey2 = HexUtil.encodeHexString(bytesToHex(pubkeyhex));
        System.out.println("生成公钥:"+publicKey);
        System.out.println("生成私钥:"+privateKey);
        //16进制字符串转**对象
        final PrivateKey privateKey2 = getPrivateKey(privateKey);
        final PublicKey publicKey2 = getPublicKey(publicKey);
        //加签验签
        final String data="需要签名的数据\b23\b21\r\n";
        final String signECDSA = signECDSA(privateKey2, data);
        System.out.println("signECDSA:"+signECDSA);
        System.out.println("data: "+data);
        final boolean verifyECDSA = verifyECDSA(publicKey2, signECDSA, data);
        System.out.println("验签结果:"+verifyECDSA);

        verifyecdsa_sign1("第一个二维码");
        verifyecdsa_sign2("第二个二维码");
 
    }
    
    public static void verifyecdsa_sign(final String data, final String pubkey, final String sign) throws Exception
    {
        System.out.println("验证公钥:"+pubkey);
        System.out.println("验证数据:"+data);
        System.out.println("验证签名:"+sign);
        final PublicKey publicKey = getPublicKey(pubkey);
        boolean verifyECDSA = verifyECDSA_jxl(publicKey, sign, data);
        System.out.println("验签结果:"+verifyECDSA);
    }
     
    public static void verifyecdsa_sign1(final String sttitle)  throws Exception 
    {
        System.out.println("验证--------------------------------------------"+sttitle );
        final String datas = "3930303034303031303032353030303120062317084014952068DB5C0989EEB00F1F128BBC990DF9C1D0CCAFFDEA3973021F945AF8B2589C955EF3181F8CA05B3059301306072A8648CE3D020106082A8648CE3D03010703420004B29046B2F93A2BFCF497390FEF8E1BFE0C34FA89D3A45AF925A22571B1C1B5E7A7409A73A7F8F9C1A61D3F9FF284926819EAEBF2E6D4968A46F112B0811A4E0501A10100";
        final String pubkey = "3059301306072A8648CE3D020106082A8648CE3D0301070342000432BA4502E1F6417C8C2D8137BE503709F1C33E1CA99C432EFC53DA8F2FF0A2AD4C4877301DEBB4522D0F733773279A8B70BA3AB2B263546F16AA0BD79F67ABC8";
        final String sign = "304402205E4799D8E8199184FB7613000781EE5D7AE2E27121733E8B391FD33C51BD80C7022039EAF55B1C3958F39764D64EC18945E1A63AB7DA2DD9D6D1BA9F1BC95FDDCCEB";
        verifyecdsa_sign(datas, pubkey, sign);
    }

    public static void verifyecdsa_sign2(final String sttitle)  throws Exception 
    {
        System.out.println("验证--------------------------------------------"+sttitle );
        final String datas = "3930303034303031303032353030303120062311462329072068DB5C0989EEB00F1F128BBC990DF9C1D0CCAFFDEA3973021F945AF8B2589C955EF2CC8F8CA05B3059301306072A8648CE3D020106082A8648CE3D03010703420004C507CABC29EAA162A33A14D190BA3100204D82E6D3F20E54BCD73A3FD78F657B67BB2E783692CB8D6A75516257633148CBA492DCE19B32410DEE1B62EAE1DAAB01A10100";
        final String pubkey = "3059301306072A8648CE3D020106082A8648CE3D0301070342000432BA4502E1F6417C8C2D8137BE503709F1C33E1CA99C432EFC53DA8F2FF0A2AD4C4877301DEBB4522D0F733773279A8B70BA3AB2B263546F16AA0BD79F67ABC8";
        final String sign = "304402209903A6794AF9E758295E486064FC6ECB63F62E5F04F66579E99A83800D32CB0902203984B7421A2E337388C782E2FF86F23D9EF313BA5D73E598D6BE2F0B99B4FE69";
        verifyecdsa_sign(datas, pubkey, sign);
    }

	/**
 	* 加签
 	* @param privateKey 私钥
 	* @param data 数据 
 	* @return
 	*/
    public static String signECDSA(final PrivateKey privateKey, final String data) {
        final String result = "";
        try {
            //执行签名
            final Signature signature = Signature.getInstance(SIGNALGORITHMS);
            signature.initSign(privateKey);
            signature.update(data.getBytes());
            final byte[] sign = signature.sign();
            return HexUtil.encodeHexString(sign);
        } catch (final Exception e) {
            e.printStackTrace();
        }
        return result;
    }
    /**
     * 验签
     * @param publicKey 公钥
     * @param signed 签名
     * @param data 数据
     * @return
     */
    public static boolean verifyECDSA_jxl(final PublicKey publicKey, final String signed, final String data) {
        try {
            //验证签名
            final Signature signature = Signature.getInstance(SIGNALGORITHMS);
            signature.initVerify(publicKey);
            final byte[] datahex = HexUtil.decode(data);
            signature.update(datahex);
            final byte[] hex = HexUtil.decode(signed);
            final boolean bool = signature.verify(hex);
            System.out.println("验证:" + bool);
            return bool;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    /**
     * 验签
     * @param publicKey 公钥
     * @param signed 签名
     * @param data 数据
     * @return
     */
    public static boolean verifyECDSA(final PublicKey publicKey, final String signed, final String data) {
        try {
            //验证签名
            final Signature signature = Signature.getInstance(SIGNALGORITHMS);
            signature.initVerify(publicKey);
            signature.update(data.getBytes());
            final byte[] hex = HexUtil.decode(signed);
            final boolean bool = signature.verify(hex);
            System.out.println("验证:" + bool);
            return bool;
        } catch (final Exception e) {
            e.printStackTrace();
        }
        return false;
    }
 
   /**
    * 从string转private key
    * @param key 私钥的字符串
    * @return
    * @throws Exception
    */
    public static PrivateKey getPrivateKey(final String key) throws Exception {
    	
        final byte[] bytes = DatatypeConverter.parseHexBinary(key);
        final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }
 
    /**
     * 从string转publicKey
     * @param key 公钥的字符串
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(final String key) throws Exception {
    	
        final byte[] bytes = DatatypeConverter.parseHexBinary(key);
        final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePublic(keySpec);
    }
 
 
    
 
    /**
     * 生成**对
     * @return
     * @throws Exception
     */
    public static KeyPair getKeyPair() throws Exception {
 
        final ECGenParameterSpec ecSpec = new ECGenParameterSpec(SECP256K1);
        final KeyPairGenerator kf = KeyPairGenerator.getInstance(ALGORITHM);
        kf.initialize(ecSpec, new SecureRandom());
        final KeyPair keyPair = kf.generateKeyPair();
        return keyPair;
    }
 
 
}

             HexUtil.java

package ecdsa;
 
/**
 * 16进制字符串与byte数组转换
 * @author Administrator
 *
 */
public final class HexUtil {
    private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
 
    public HexUtil() {
    }
 
    /**
     * byte数组转16进制字符串
     * @param bytes
     * @return
     */
    public static String encodeHexString(byte[] bytes) {
        int nBytes = bytes.length;
        char[] result = new char[2 * nBytes];
        int j = 0;
        byte[] var4 = bytes;
        int var5 = bytes.length;
 
        for(int var6 = 0; var6 < var5; ++var6) {
            byte aByte = var4[var6];
            result[j++] = HEX[(240 & aByte) >>> 4];
            result[j++] = HEX[15 & aByte];
        }
 
        return new String(result);
    }
 
    /**
     * 16进制字符串转byte数组
     * @param s 字符串
     * @return
     */
    public static byte[] decode(CharSequence s) {
        int nChars = s.length();
        if (nChars % 2 != 0) {
            throw new IllegalArgumentException("Hex-encoded string must have an even number of characters");
        } else {
            byte[] result = new byte[nChars / 2];
 
            for(int i = 0; i < nChars; i += 2) {
                int msb = Character.digit(s.charAt(i), 16);
                int lsb = Character.digit(s.charAt(i + 1), 16);
                if (msb < 0 || lsb < 0) {
                    throw new IllegalArgumentException("Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position");
                }
 
                result[i / 2] = (byte)(msb << 4 | lsb);
            }
 
            return result;
        }
    }
}

        验证结果:两套数据验签都是通过的

 OPENSSL: ECDSA算法验签之签名数据的DER格式同一处理

4、通过调试openssl源码可知

签名的数据:

304402209903A6794AF9E758295E486064FC6ECB63F62E5F04F66579E99A83800D32CB0902203984B7421A2E337388C782E2FF86F23D9EF313BA5D73E598D6BE2F0B99B4FE69

在 ossl_ecdsa_verify 接口中校验DER签名序列时保存,究其原因,原签名数据和 i2d_ECDSA_SIG 转换的签名数据不一致如下:

30450221009903A6794AF9E758295E486064FC6ECB63F62E5F04F66579E99A83800D32CB0902203984B7421A2E337388C782E2FF86F23D9EF313BA5D73E598D6BE2F0B99B4FE69

两者对比差异:

OPENSSL: ECDSA算法验签之签名数据的DER格式同一处理

附带DER签名序列的规则

//Sig = (R, S)
//签名序列化(DER)
//解锁脚本序列化之后:
//3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301
/包含以下9个元素:
    //● 0x30表示DER序列的开始
    //● 0x45 - 序列的长度(69字节)
    //● 0x02 - 一个整数值
    //● 0x21 - 整数的长度(33字节)
    //● R-00884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb
    //● 0x02 - 接下来是一个整数
    //● 0x20 - 整数的长度(32字节)
    //● S-4b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813
    //● 后缀(0x01)指示使用的哈希的类型(SIGHASH_ALL)

 5、为了项目能顺利使用,最快速的处理方法(统一格式化签名数据格式)

通过下面的接口,统一签名的数据,如下:


/**********************************************************************************************//**
*  @fn       static int Alg_Statistical_ECDSA_DER_Sign_format(const uint8_t *pucSigInBuf, int iSigInLen, uint8_t *pucSigOutBuf, int *piSigOutLen);
*  @brief    统一ECDSA的签名数据格式(java计算的签名格式有时候不统一)
*  
*  @date     2020年6月28日 11:20:32
*  @param   [in] pucSigInBuf
*  @param   [in] iSigInLen
*  @param   [out] pucSigOutBuf
*  @param   [out] piSigOutLen
*  @return   -    0  成功
*            -    -1 失败
***************************************************************************************************/
static int Alg_Statistical_ECDSA_DER_Sign_format(const uint8_t *pucSigInBuf, int iSigInLen, uint8_t *pucSigOutBuf, int *piSigOutLen)
{
    ECDSA_SIG *s;
    uint8_t *pucDestSign = NULL;
    int iDestSignLen = -1;
    int iRet = -1;
    
	Comm_logh("IN-SIGN", pucSigInBuf, iSigInLen);

    s = ECDSA_SIG_new();
    if (s == NULL)
    {
        return iRet;
    }
    if (d2i_ECDSA_SIG(&s, &pucSigInBuf, iSigInLen) == NULL)
    {
        return iRet;
    }

    /* Ensure signature uses DER and doesn't have trailing garbage */
    iDestSignLen = i2d_ECDSA_SIG(s, &pucDestSign);
    *piSigOutLen = iDestSignLen;
    memcpy(pucSigOutBuf, pucDestSign, *piSigOutLen);
    
    Comm_logh("OUT-SIGN", pucSigOutBuf, *piSigOutLen);

    OPENSSL_free(pucDestSign);
    ECDSA_SIG_free(s);
    
    return (0);
}

验证结果:

OPENSSL: ECDSA算法验签之签名数据的DER格式同一处理

备注:关于DER的规则,为什么java和c处理的格式有部分一样,有部分不一样,这块没做深的研究,项目时间比较紧张,待有空了研究下补充此部分差异

相关标签: openssl 算法