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

原文链接

背景介绍

1、SHA

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

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

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

2、消息摘要

定义:

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

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

类型:

摘要:GOST3411,Keccak,MD2,MD4,MD5,RIPEMD128,RIPEMD160,RIPEMD256,RIPEMD320,SHA-1,SHA-224,SHA-256,SHA-384,SHA-512,SHA3,Tiger和Whirlpool。

3、公钥和私钥:

类型:

对称密钥算法: 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.
对称密钥模式:CBC,CFB,CTS,GOFB,OFB,OpenPGPCFB和SIC(或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.

4、数字签名

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

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-1:

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

SHA-1有如下特性:

不可以从消息摘要中复原信息;
两个不同的消息不会产生同样的消息摘要,(但会有1x10 ^ 48分之一的机率出现相同的消息摘要,一般使用时忽略)。
利用SHA-1算法和RSA秘钥进行签名验签。

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");
            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;
    }
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值