密码学知识Android开发Android开发

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

2018-01-27  本文已影响2991人  夏日里的故事

1. 概述


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

2. Signature类


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

该类的主要方法如下:

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

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

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

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

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

校验签名,当所有的数据都调用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加密算法入门介绍
上一篇下一篇

猜你喜欢

热点阅读