利用SHA-1算法和RSA秘钥进行签名验签

背景介绍

SHA

安全散列算法SHA (Secure Hash Algorithm)是美国国家标准和技术局发布的国家标准FIPS PUB 180-1,一般称为SHA-1。其对长度不超过264二进制位的消息产生160位的消息摘要输出,按512比特块处理其输入。

SHA是一种数据加密算法,该算法经过加密专家多年来的发展和改进已日益完善,现在已成为公认的最安全的散列算法之一,并被广泛使用。

该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。散列函数值可以说时对明文的一种“指纹”或是“摘要”所以对散列值的数字签名就可以视为对此明文的数字签名。

消息摘要

定义:

消息摘要(Message Digest)又称为数字摘要(Digital Digest)。它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生。如果消息在途中改变了,则接收者通过对收到消息的新产生的摘要与原摘要比较,就可知道消息是否被改变了。因此消息摘要保证了消息的完整性。

消息摘要采用单向Hash函数将需加密的明文”摘要”成一串128bit的密文,这一串密文亦称为数字指纹(Finger Print),它有固定的长度,且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致。这样这串摘要便可成为验证明文是否是”真身”的”指纹”了。

类型:

摘要GOST3411KeccakMD2MD4MD5RIPEMD128RIPEMD160RIPEMD256RIPEMD320SHA-1SHA-224SHA-256SHA-384SHA-512SHA3TigerWhirlpool

公钥和私钥:

定义:

公钥和私钥就是俗称的不对称加密方式,是从以前的对称加密(使用用户名与密码)方式的提高。

下面用电子邮件的方式说明一下原理:

使用公钥与私钥的目的就是实现安全的电子邮件,必须实现如下目的:

  • 1、我发送给你的内容必须加密,在邮件的传输过程中不能被别人看到。
  • 2、必须保证是我发送的邮件,不是别人冒充我的。

    要达到这样的目标必须发送邮件的两人都有公钥和私钥。


  • 公钥: 就是给大家用的,你可以通过电子邮件发布,可以通过网站让别人下载,公钥其实是用来加密/验章用的。
  • 私钥: 就是自己的,必须非常小心保存,最好加上密码,私钥是用来解密/签章,首先就Key的所有权来说,私钥只有个人拥有。
  • 公钥与私钥的作用是: 用公钥加密的内容只能用私钥解密,用私钥加密的内容只能用公钥解密。

  • 举例:

      比如说,我要给你发送一个加密的邮件。首先,我必须拥有你的公钥,你也必须拥有我的公钥。

      首先,我用你的公钥给这个邮件加密,这样就保证这个邮件不被别人看到,而且保证这个邮件在传送过程中没有被修改。你收到邮件后,用你的私钥就可以解密,就能看到内容。

      其次我用我的私钥给这个邮件加密,发送到你手里后,你可以用我的公钥解密。因为私钥只有我手里有,这样就保证了这个邮件是我发送的。

      当A->B资料时,A会使用B的公钥加密,这样才能确保只有B能解开,否则普罗大众都能解开加密的讯息,就是去了资料的保密性。验证方面则是使用签验章的机制,A传资料给大家时,会以自己的私钥做签章,如此所有收到讯息的人都可以用A的公钥进行验章,便可确认讯息是由A发出来的了。

    类型:

  • 对称密钥算法: AES, Blowfish, Camellia, CAST5, CAST6,ChaCha, DES, DESede, GOST28147, HC-128, HC-256, IDEA, ISAAC, Noekeon, RC2, RC4, RC5-32, RC5-64, RC6, Rijndael, Salsa20, SEED, Serpent, Skipjack, TEA/XTEA, Threefish, Tnepres, Twofish, VMPC and XSalsa20.
  • 对称密钥模式:CBCCFBCTSGOFBOFBOpenPGPCFBSIC(或CTR)
  • 对称密钥填充: ISO10126d2, ISO7816d4, PKCS-5/7, TBC, X.923, and Zero Byte.
  • 非对称密钥算法: ElGamal, DSA, ECDSA, NaccacheStern and RSA (with blinding).
  • 非对称密钥填充/编码:ISO9796d1, OAEP, and PKCS-1.

    数字签名

    电子商务中数据传输的几个安全性需求

  • 1、数据的保密性:用于防止非法用户进入系统及合法用户对系统资源的非法使用;通过对一些敏感的数据文件进行加密来保护系统之间的数据交换,防止除接收方之外的第三方截获数据及即使获取文件也无法得到其内容。如在电子交易中,避免遭到黑客的袭击使信用卡信息丢失的问题。
  • 2、数据的完整性:防止非法用户对进行交换的数据进行无意或恶意的修改、插入,防止交换的数据丢失等。
  • 3、数据的不可否认性:对数据和信息的来源进行验证,以确保数据由合法的用户发出;防止数据发送方在发出数据后又加以否认;同时防止接收方在收到数据后又否认曾收到过此数据及篡改数据。

==注:== 上述需求对应于防火墙、加密、数字签名、身份认证等技术,但其关键在于数字签名技术。

数字签名的含义

 数字签名是通过一个单向函数对要传送的报文进行处理得到的用以认证报文来源并核实报文是否发生变化的一个字母数字串。

数字签名的实现方法

实现数字签名有很多方法,目前数字签名采用较多的是公钥加密技术,如基于RSA Date Security 公司的PKCS( Public Key Cryptography Standards )、Digital Signature Algorithm、x.509、PGP(Pretty Good Privacy)。

1994年美国标准与技术协会公布了数字签名标准(DSS)而使公钥加密技术广泛应用。公钥加密系统采用的是非对称加密算法。

由SignerUtilities支持的签名算法

MD2withRSA, MD4withRSA,MD5withRSA,
RIPEMD128withRSA, RIPEMD160withECDSA, RIPEMD160withRSA,
RIPEMD256withRSA, SHA-1withRSA, SHA-224withRSA,
SHA-256withRSAandMGF1, SHA-384withRSAandMGF1, SHA-512withRSAandMGF1,
SHA-1withDSA, and SHA-1withECDSA

使用范例:(带注释)

SHA-1:

对于长度小于2^64位的消息,SHA1会产生一个160位(40个字符)的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。

SHA-1有如下特性:

  • 不可以从消息摘要中复原信息;
  • 两个不同的消息不会产生同样的消息摘要,(但会有1x10 ^ 48分之一的机率出现相同的消息摘要,一般使用时忽略)。

    利用SHA-1算法和RSA秘钥进行签名验签

    代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    import javax.crypto.Cipher;
    import java.io.*;
    import java.security.*;
    import java.util.Base64;

    /**
    * @author: mmzsit
    * @date: 2018年10月24日
    * @Description:
    * 博客地址:https://blog.mmzsblog.cn
    * @version V1.0
    */
    public class EncryptUtil {

    public static void main(String[] args) {
    ObjectInputStream inputStream = null;

    //参数字符串
    String userName="测试test0->1";
    String orderId="测试ID123456";
    String price="666";
    //构建用于签名和传输的字符串
    StringBuffer bufferStr =new StringBuffer();
    bufferStr.append("userName=").append(userName)
    .append("&orderId=").append(orderId)
    .append("&price=").append(price);
    //将构建的字符串转化为String类型
    String localStr =bufferStr.toString();

    //签名算法加密
    try {
    //随机生成秘钥对
    // 检查是否存在这对密钥,否则生成这些密钥
    if (!areKeysPresent()) {
    // 使用RSA算法生成一对密钥,并存储在指定路径的指定文件中
    generateKey();
    }

    //服务端数字签名开始
    //第一步:用SHA-1算出原文的摘要
    byte[] shaDigest = shaEncrypt(localStr);
    System.out.println("原文本内容:\n"+localStr);
    String shaStr = new String(shaDigest,"UTF-8");
    System.out.println("原文本内容SHA-1算法后:\n"+shaStr);

    //第二步:使用私钥对原文进行加密
    //读取文件中的私钥
    inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
    final PrivateKey privateKey = (PrivateKey) inputStream.readObject();
    //使用私钥加密
    byte[] rsaBytes = rsaEncrypt(shaDigest,privateKey);

    //第三步:对密文进行BASE64编码
    byte[] base64Str = Base64.getEncoder().encode(rsaBytes);
    String base64enCode=new String(base64Str,"UTF-8");
    //一般长度是172
    System.out.println("加密后的内容长度:\n"+base64enCode.length());
    System.out.println("加密后的内容:\n"+base64enCode);

    //签名加密完成数据传输到客户端

    //客户端验证签名开始
    //获取原文
    String receiveStr=localStr;
    //第一步:使用Base64解码密文
    byte[] bese64Decoded =Base64.getDecoder().decode(base64enCode.getBytes("UTF-8"));

    //第二步:使用公钥对密文进行解码
    //读取文件中的公钥
    inputStream = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE));
    final PublicKey publicKey = (PublicKey) inputStream.readObject();
    //使用公钥解密
    byte[] rsaDecode = rsaDecrypt(bese64Decoded,publicKey);
    //公钥解密后的结果
    String base64denCode=new String(rsaDecode,"utf-8");
    System.out.println("公钥解密后的结果:\n"+base64denCode);

    //第三步:验签
    //读取解密后的摘要
    String sha1=Base64.getEncoder().encodeToString(rsaDecode);
    //使用Sha-1对原文计算摘要
    MessageDigest md =MessageDigest.getInstance("SHA-1");
    String sha2=Base64.getEncoder().encodeToString(md.digest(receiveStr.getBytes("utf-8")));
    //用Sha-1对原文计算摘要结果和解密后的明文比对
    if(sha1.equals(sha2)) {
    System.out.println("验签成功");
    } else {
    System.out.println("验签失败");
    }

    System.out.println("字符串sha1:\n"+sha1);
    System.out.println("字符串sha2:\n"+sha2);

    }catch (Exception e) {
    e.printStackTrace();
    }


    }

    /**
    * 用于保存加密算法名称的字符串
    */
    public static final String ALGORITHM = "RSA";

    /**
    *
    * 用于保存加密填充名称的字符串
    * 如果不填写,那么RSA/NONE/NoPadding就是Bouncy Castle 的默认 RSA 实现
    * 备用:
    */
    public static final String PADDING = "RSA/ECB/PKCS1Padding";

    /**
    * 用于保存安全提供程序名称的字符串
    */
    // public static final String PROVIDER = "BC";

    /**
    * 用于保存私钥文件名称的字符串
    */
    public static final String PRIVATE_KEY_FILE = "d:/Temp/private.key";

    /**
    * 用于保存公钥文件名称的字符串
    */
    public static final String PUBLIC_KEY_FILE = "d:/Temp/public.key";

    /**
    * 假设最高安全性(即4096位RSA密钥或更大)是非常安全
    * 使用1024字节生成包含一对私钥和公钥的密钥。
    * 将该组密钥存储在Prvate.key和Public.key文件中。
    *
    * @throws NoSuchAlgorithmException
    * @throws IOException
    * @throws FileNotFoundException
    */
    public static void generateKey() {
    try {

    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
    //final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
    //密钥位数
    keyGen.initialize(1024);
    //密钥对
    final KeyPair key = keyGen.generateKeyPair();

    File privateKeyFile = new File(PRIVATE_KEY_FILE);
    File publicKeyFile = new File(PUBLIC_KEY_FILE);

    // 创建文件夹存储私钥
    if (privateKeyFile.getParentFile() != null) {
    privateKeyFile.getParentFile().mkdirs();
    }
    privateKeyFile.createNewFile();
    // 创建文件夹存储公钥
    if (publicKeyFile.getParentFile() != null) {
    publicKeyFile.getParentFile().mkdirs();
    }
    publicKeyFile.createNewFile();

    // 创建文件夹保存公钥
    ObjectOutputStream publicKeyOS = new ObjectOutputStream(
    new FileOutputStream(publicKeyFile));
    publicKeyOS.writeObject(key.getPublic());
    publicKeyOS.close();

    // 创建文件夹保存私钥
    ObjectOutputStream privateKeyOS = new ObjectOutputStream(
    new FileOutputStream(privateKeyFile));
    privateKeyOS.writeObject(key.getPrivate());
    privateKeyOS.close();
    } catch (Exception e) {
    e.printStackTrace();
    }

    }

    /**
    * 检查是否已生成一对公钥和私钥
    *
    * @return boolean 返回是否生成秘钥对的标识
    */
    public static boolean areKeysPresent() {

    File privateKey = new File(PRIVATE_KEY_FILE);
    File publicKey = new File(PUBLIC_KEY_FILE);

    if (privateKey.exists() && publicKey.exists()) {
    return true;
    }
    return false;
    }

    /**
    * 使用公钥解密数据
    *
    * @param text 待解密文本
    * @param key 公钥
    * @return 解密文本
    * @throws java.lang.Exception
    */
    public static byte[] rsaDecrypt(byte[] text, PublicKey key) {
    byte[] cipherText = null;
    try {
    // 获取RSA密码对象并打印提供程序
    // Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    final Cipher cipher = Cipher.getInstance(PADDING);
    //final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);

    // 使用公钥,ENCRYPT_MODE表示为解密模式
    cipher.init(Cipher.DECRYPT_MODE, key);
    cipherText = cipher.doFinal(text);
    } catch (Exception e) {
    e.printStackTrace();
    }
    return cipherText;
    }

    /**
    * 使用私钥加密数据
    *
    * @param text 待加密文本
    * @param key 私钥
    * @return 加密后的数据
    * @throws java.lang.Exception
    */
    public static byte[] rsaEncrypt(byte[] text, PrivateKey key) {
    byte[] dectyptedText = null;
    try {
    // //获取RSA密码对象并打印提供程序
    // Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    final Cipher cipher = Cipher.getInstance(PADDING);
    //final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);

    // 使用私钥加密文本
    cipher.init(Cipher.ENCRYPT_MODE, key);
    dectyptedText = cipher.doFinal(text);

    } catch (Exception ex) {
    ex.printStackTrace();
    }

    return dectyptedText;
    }

    /**
    * 使用sha-1对摘要进行加密
    * @param text 签名的原始文本
    */
    public static byte[] shaEncrypt(String text) {
    //创建消息摘要算法的类
    MessageDigest md = null;
    //由于接收加密后的摘要的字节数组
    byte[] shaDigest = null;
    try {
    //使用getInstance("算法")来获得消息摘要
    md = MessageDigest.getInstance("SHA-1");
    //将摘要转化为UTF-8格式的字节数组
    byte[] plainText = text.getBytes("UTF-8");
    //使用指定的 byte 数组更新摘要
    md.update(plainText);
    //得出SHA-1算法加密后的结果
    shaDigest=md.digest();
    } catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    }
    return shaDigest;
    }

    }

参考文章: