美文网首页密码学知识Android开发Android开发
Java密码学 非对称加密以及使用secp256k1进行数字签名

Java密码学 非对称加密以及使用secp256k1进行数字签名

作者: 夏日里的故事 | 来源:发表于2018-01-27 16:19 被阅读2991次

    1. 概述


    上篇讲述了秘钥的生成、存储和加载,这篇的内容就是如何生成和校验数字签名。

    2. Signature类


    在Java中,签名和校验,都是通过: Signature 类来实现的。

    该类的主要方法如下:

    • getInstance(String algorithm)

    工厂方法,获取Signature实例,而参数:algorithm就是签名算法的名称,这里我们使用的是:SHA256withECDSA

    更具体的参数,请浏览:https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Signature

    • initSign(PrivateKey privateKey)
    • initVerify(PublicKey publicKey)

    初始化成签名或者校验,Signature类的实例,在同一个阶段只能完成签名或者校验之一的任务,因此调用initSign或者initVeify让Signature进入相应的状态

    • update(byte[] data)

    无论是计算签名,还是校验数据,都需要传入被签名或者被校验的数据,调用该方法进行计算。注意,该方法可以调用多次,通常数据都可能非常大,不可能一次性读入内存(从磁盘上或者网络),因此我们可以对数据进行分块,一次性一KB或者合适的块进行多次调用

    • sign()

    获取签名,当所有的数据都调用update计算后,就可以获取签名了,返回的签名是一个byte数组

    • verify(byte[] signature)

    校验签名,当所有的数据都调用update计算之后,调用该方法,传递签名数据,如果签名正确,那么返回true,否则返回false,说明数据可能被篡改、伪造,或者对应的私钥不正确。

    3. 实例


    Java中计算签名比较简单,请阅读下面代码中的注释

    public static byte[] signData(String algorithm, byte[] data, PrivateKey key) throws Exception {
            Signature signer = Signature.getInstance(algorithm);
            signer.initSign(key);
            signer.update(data);
            return (signer.sign());
        }
    
        public static boolean verifySign(String algorithm, byte[] data, PublicKey key, byte[] sig) throws Exception {
            Signature signer = Signature.getInstance(algorithm);
            signer.initVerify(key);
            signer.update(data);
            return (signer.verify(sig));
        }
    
        @Test
        public void testSignVerify() throws Exception {
            // 需要签名的数据
            byte[] data = new byte[1000];
            for (int i=0; i<data.length; i++)
                data[i] = 0xa;
    
            // 生成秘钥,在实际业务中,应该加载秘钥
            KeyPair keyPair = KeyUtil.createKeyPairGenerator("secp256k1");
            PublicKey publicKey1 = keyPair.getPublic();
            PrivateKey privateKey1 = keyPair.getPrivate();
    
            // 生成第二对秘钥,用于测试
            keyPair = KeyUtil.createKeyPairGenerator("secp256k1");
            PublicKey publicKey2 = keyPair.getPublic();
            PrivateKey privateKey2 = keyPair.getPrivate();
    
            // 计算签名
            byte[] sign1 = signData("SHA256withECDSA", data, privateKey1);
            byte[] sign2 = signData("SHA256withECDSA", data, privateKey1);
    
            // sign1和sign2的内容不同,因为ECDSA在计算的时候,加入了随机数k,因此每次的值不一样
            // 随机数k需要保密,并且每次不同
    
    
            // 用对应的公钥验证签名,必须返回true
            Assert.assertTrue(verifySign("SHA256withECDSA", data, publicKey1, sign1));
            // 数据被篡改,返回false
            data[1] = 0xb;
            Assert.assertFalse(verifySign("SHA256withECDSA", data, publicKey1, sign1));
            data[1] = 0xa;
            
            Assert.assertTrue(verifySign("SHA256withECDSA", data, publicKey1, sign1));
            // 签名被篡改,返回false
            // 签名为DER格式,前三个字节是标识和数据长度,如果修改了这三个会抛出异常,无效签名格式
            sign1[20] = (byte)~sign1[20];
            Assert.assertFalse(verifySign("SHA256withECDSA", data, publicKey1, sign1));
    
            // 使用其他公钥验证,返回false
            Assert.assertFalse(verifySign("SHA256withECDSA", data, publicKey2, sign1));
        }
    

    我们在测试用例中可以看到,只要是数据、签名被修改,或者不正确的私钥都会引发签名验证失败.

    4. 签名输出数据解析


    针对 SHA256withECDSA ,输出的是DER编码的签名数据,长度并非固定,但是是70到72字节,之所以会这样是因为,SHA256withECDSA 签名输出实际是两个32字节的大整数(r和s),在转换成byte[]的时候,如果为负数,那么会添加前导位0,如果当r和s都是正数,那么就是64个字节,都是负数,就是66字节。而DER编码还会包含类型以及长度字段,因此总长度就会到70到72字节。一般情况下,传输DER编码的签名值没多大问题,但如果对数据量要求十分严格,例如在BLE上传输,可以提取出r和s再打包传输

    5. 参考


    1. Java SE 7 Security Documentation
    2. 数字签名是什么?
    3. Android签名机制之---签名过程详解
    4. ECC加密算法入门介绍

    相关文章

      网友评论

        本文标题:Java密码学 非对称加密以及使用secp256k1进行数字签名

        本文链接:https://www.haomeiwen.com/subject/bbunaxtx.html