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

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";

        执行结果如下:

       

       结论: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;
        }
    }
}

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

 

4、通过调试openssl源码可知

签名的数据:

304402209903A6794AF9E758295E486064FC6ECB63F62E5F04F66579E99A83800D32CB0902203984B7421A2E337388C782E2FF86F23D9EF313BA5D73E598D6BE2F0B99B4FE69

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

30450221009903A6794AF9E758295E486064FC6ECB63F62E5F04F66579E99A83800D32CB0902203984B7421A2E337388C782E2FF86F23D9EF313BA5D73E598D6BE2F0B99B4FE69

两者对比差异:

附带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);
}

验证结果:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值