RSA数据摘要+数字签名(Java)

背景

之前我一直不明白,比如A、B双方加密通信的时候,为什么A要使用B的公钥来加密,为A什么不使用A的私钥加密,然后B用A的公钥解密不就可以了吗?

其实,A的私钥主要是A用来签名的,签名顾名思义是表明这个东西是A发送的,而不是别人发过来的。为什么A用自己的私钥签名就说明这个东西就是A发过来的呢?因为用B用A的公钥解签的时候,得到了原文的数据摘要,然后B根据得到的明文信息然后使用Hash函数重新得到数据摘要,两个数据摘要一对比,如果一致,那么就是A发过来的,表明了即使A发过来的且原文信息没有被改动过。

具体流程图下图所示:

RSA数据摘要+数字签名(Java)


目标

使用Java语言,实现RSA数据摘要+数据签名。


代码

程序入口文件:MainEntrance

package RSA;



/**
 * @author yourname
 * @date 2018年10月16日 下午5:12:18
 * 
 */
public class MainEntrance {

	
	 public static void main(String[] args) {
	        
	        try {
				new Test();
				
	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	    }
}

窗口界面以及事件处理文件:Test

package RSA;

import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
import javax.swing.JTextField;

/**
 * @author yourname
 * @date 2018年10月16日 下午6:45:43
 * 
 */
public class Test extends JFrame {


    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	String a2="";
     int pd;
     Map<String, Object> keyMap;
     String  textPlainMD5 = "";
     String byte2Base64 = "";
     byte[] publicDecrypt = null;
     
    public Test() throws Exception
    {
    		//使用Map存放初始化的公钥和私钥
    		keyMap = CreateSecrteKey.initKey();
    	
    		//创建容器和控件
        Container c1 = getContentPane();
        getContentPane().setLayout(null);//布局
        getContentPane().setBackground(Color.LIGHT_GRAY);
        JLabel jl1 =new JLabel("请输入明文:");
        JLabel jl2 =new JLabel("选择方式:"); 
        JLabel jl3 =new JLabel("结果:");  

        JTextField jt1 = new JTextField(10);
        
        JLabel jt3 = new JLabel("");
        //设置jt3的字体
        jt3.setFont(new Font("宋体",Font.BOLD, 8));


         JButton jb = new JButton("开始");
         jb.setBorderPainted(true);//设置按钮显示边界
         
         JButton jb1 = new JButton("反解");
         jb1.setBorderPainted(true);//设置按钮显示边界

         JRadioButton jr1 = new JRadioButton("提取数据摘要(MD5)");//单选按钮
         JRadioButton jr2 = new JRadioButton("RSA数据签名");
         ButtonGroup group = new ButtonGroup();//没有这句,你的单选按钮就不叫单选按钮,可以自己去掉试试
         group.add(jr1);
         group.add(jr2);
         
    			//控件加入到容器
            c1.add(jl1);
            c1.add(jt1);
            c1.add(jl2);
            c1.add(jr1);
            c1.add(jr2);
            c1.add(jl3);
            c1.add(jt3);
            c1.add(jb);
            c1.add(jb1);
            setSize(400, 200);
            setVisible(true);
            setTitle("RSA签名算法");
            
            //设置各个控件的绝对位置
            jl1.setBounds(5,5,80,20);
            
            jt1.setBounds(85, 5, 150, 20);
            
            jl2.setBounds(5, 25,	80, 20);
            
            jr1.setBounds(85, 25, 160, 25);
            
            jr2.setBounds(85,65,140,25);
            
            jl3.setBounds(5,95,80,25);
            
            jt3.setBounds(85, 95, 200, 70);
            jt3.setOpaque(true);
            jt3.setBackground(Color.cyan);
            
            jb.setBounds(285, 95, 60, 30);
            
            jb1.setBounds(285,125,60,30);
            
            //"反解按钮"监听事件
            jb1.addActionListener(
            		new ActionListener() {
						
						@Override
						public void actionPerformed(ActionEvent e) {
							// TODO Auto-generated method stub
							//对数据摘要进行Hash对比
							if(pd==1)
							{
								if(textPlainMD5.length()>0)
								{
									String e1 = jt1.getText();//输入的数据
									//使用MD5对原文信息提取数据摘要
									String textPlainMD51 = CreateSecrteKey.MD5(e1);
									//把解签得到的Hash值和原文重新MD5生成的Hash值对比
									if(new String(publicDecrypt).equals(textPlainMD51))
									{
										System.out.println("原文信息的Hash值和解签得到的Hash一样的");
										try {
											JlabelSetText(jt3, "原文信息的Hash值和解签得到的Hash一样的");
										} catch (InterruptedException e2) {
											// TODO Auto-generated catch block
											e2.printStackTrace();
										}
									}
								}
								else
								{
									JOptionPane.showMessageDialog(null, "请先完成数据摘要提取和数据签名!", "提示", JOptionPane.ERROR_MESSAGE);
								}
							}
							//对数据签名进行解签
							else if(pd==2)
							{
								if(byte2Base64.length()>0)
								{
									try {
	                      	            //获得公钥
	                      	            String publicKey_Str = CreateSecrteKey.getPublicKey(keyMap);
//	                      	            System.out.println(publicKey_Str);
	                      	            //获得公钥PK对象
	                      	            PublicKey publicKey = CreateSecrteKey.string2PublicKey(publicKey_Str);
	                      	            //对密文进行Base64解码
	                      	            byte[] base642Byte = CreateSecrteKey.base642Byte(byte2Base64);
	                      	            //用公钥解签
	                      	             publicDecrypt = CreateSecrteKey.publicDecrypt(base642Byte, publicKey);
	                      	            //解密后的数据摘要
//	                      				System.out.println("解密后的MD5: " + new String(publicDecrypt));
	                      	            JlabelSetText(jt3, new String(publicDecrypt));
									} catch (Exception e2) {
										// TODO: handle exception
									}
								}
								else
								{
									JOptionPane.showMessageDialog(null, "请先完成数据摘要提取和数据签名!", "提示", JOptionPane.ERROR_MESSAGE);
								}
							}
						}
					}
            		);
            
            
          //"开始按钮"转换按钮添加监听事件
            jb.addActionListener(
                    new ActionListener(){

                        public void actionPerformed(ActionEvent arg0){
                        	//弹出对话框
                        String e1 = jt1.getText();//输入的数据                         
//                        System.out.println("e1:"+e1);
                         //如果没有输入明文信息,就提示错误    
                        	if(e1.equals(""))
                        	{
                        		JOptionPane.showMessageDialog(null, "请先输入明文信息!", "提示", JOptionPane.ERROR_MESSAGE);
                        	} 
                        	//选择提取数据摘要的按钮处理事件
                        	if(pd==1&&e1.length()>0)
                        	{
                        		//使用MD5对原文信息提取数据摘要
                	            textPlainMD5 = CreateSecrteKey.MD5(e1);
                	            //显示摘要信息,并自动换行
                	            try {
    								JlabelSetText(jt3, textPlainMD5);
    							} catch (InterruptedException e) {
    								// TODO Auto-generated catch block
    								e.printStackTrace();
    							}
                        	}
                        	//选择RSA数据签名的按钮处理事件
                        	else if(pd==2&&e1.length()>0)
                        	{
                      	        try {
                      	            //获得私钥
                      	            String privateKey_Str = CreateSecrteKey.getPrivateKey(keyMap);
//                      	            System.out.println(privateKey_Str);
                      	            
                      	            //获得私钥PK对象
                      	            PrivateKey privateKey = CreateSecrteKey.string2PrivateKey(privateKey_Str);

                      	            //用私钥签名
                      	            byte[] privateEncrypt = CreateSecrteKey.privateEncrypt(textPlainMD5.getBytes(), privateKey);
                      	            //加密内容用Base64编码(密文)
                      	             byte2Base64 = CreateSecrteKey.byte2Base64(privateEncrypt);
//                      	            System.out.println("数据签名:"+byte2Base64);
                      	            
                      	            //显示签名信息,并自动换行
                      	            JlabelSetText(jt3, byte2Base64);
                      				
                      	        } catch (Exception e) {
                      	            e.printStackTrace();
                      	        }                  	    
                        	}
                        	}
                    });

        jr1.addActionListener(//提取数据摘要按钮
                new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        // TODO 自动生成的方法存根
                    	//提取数据摘要时候标记为1
                        pd=1;
                    }
                }
                );
        jr2.addActionListener(//RSA数据签名按钮
                new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        // TODO 自动生成的方法存根
                    	//数据签名的时候标记为2
                        pd=2;
                    }
                }
                );    
    }
    
    
    
    
    
    
    //让JLabel自动换行
    void JlabelSetText(JLabel jLabel, String longString) 
            throws InterruptedException {
        StringBuilder builder = new StringBuilder("<html>");
        char[] chars = longString.toCharArray();
        FontMetrics fontMetrics = jLabel.getFontMetrics(jLabel.getFont());
        int start = 0;
        int len = 0;
        while (start + len < longString.length()) {
            while (true) {
                len++;
                if (start + len > longString.length())break;
                if (fontMetrics.charsWidth(chars, start, len) 
                        > jLabel.getWidth()) {
                    break;
                }
            }
            builder.append(chars, start, len-1).append("<br/>");
            start = start + len - 1;
            len = 0;
        }
        builder.append(chars, start, longString.length()-start);
        builder.append("</html>");
        jLabel.setText(builder.toString());
    }
    
    
}

工具类文件(公私钥生成、MD5、Base64等):CreateSecrteKey

package RSA;

import java.io.IOException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;


/**
 * @author yourname
 * @date 2018年10月17日 上午12:26:27
 * 
 */
public class CreateSecrteKey {
public class Keys {
        
    }
	//使用RSA算法
    public static final String KEY_ALGORITHM = "RSA";
    //公钥
    private static final String PUBLIC_KEY = "RSAPublicKey";
    //私钥
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    //获得公钥
    public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
        //获得map中的公钥对象 转为key对象
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        //byte[] publicKey = key.getEncoded();
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    //获得私钥
    public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
        //获得map中的私钥对象 转为key对象
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        //byte[] privateKey = key.getEncoded();
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    //解码返回byte
    public static byte[] decryptBASE64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }

    //编码返回字符串
    public static String encryptBASE64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }
    
    //map对象中存放公私钥
    public static Map<String, Object> initKey() throws Exception {
        //获得对象 KeyPairGenerator 参数 RSA 1024个字节
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        //通过对象 KeyPairGenerator 获取对象KeyPair
        KeyPair keyPair = keyPairGen.generateKeyPair();
        
        //通过对象 KeyPair 获取RSA公私钥对象RSAPublicKey RSAPrivateKey
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        //公私钥对象存入map中
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    //私钥加密
  	public static byte[] privateEncrypt(byte[] content, PrivateKey privateKey) throws Exception{
  		Cipher cipher = Cipher.getInstance("RSA");
  		cipher.init(Cipher.ENCRYPT_MODE, privateKey);
  		byte[] bytes = cipher.doFinal(content);
  		return bytes;
  	}
  	
  	//公钥解密
  	public static byte[] publicDecrypt(byte[] content, PublicKey publicKey) throws Exception{
  		Cipher cipher = Cipher.getInstance("RSA");
  		cipher.init(Cipher.DECRYPT_MODE, publicKey);
  		byte[] bytes = cipher.doFinal(content);
  		return bytes;
  	}
  	
  	//字节数组转Base64编码
  	public static String byte2Base64(byte[] bytes){
  		BASE64Encoder encoder = new BASE64Encoder();
  		return encoder.encode(bytes);
  	}
  	
  	//Base64编码转字节数组
  	public static byte[] base642Byte(String base64Key) throws IOException{
  		BASE64Decoder decoder = new BASE64Decoder();
  		return decoder.decodeBuffer(base64Key);
  	}

	//将Base64编码后的公钥转换成PublicKey对象
	public static PublicKey string2PublicKey(String pubStr) throws Exception{
		byte[] keyBytes = base642Byte(pubStr);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		PublicKey publicKey = keyFactory.generatePublic(keySpec);
		return publicKey;
	}
	
	//将Base64编码后的私钥转换成PrivateKey对象
	public static PrivateKey string2PrivateKey(String priStr) throws Exception{
		byte[] keyBytes = base642Byte(priStr);
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
		return privateKey;
	}

	public static String MD5(String s) {
	    try {
	        MessageDigest md = MessageDigest.getInstance("MD5");
	        byte[] bytes = md.digest(s.getBytes("utf-8"));
	        return toHex(bytes);
	    }
	    catch (Exception e) {
	        throw new RuntimeException(e);
	    }
	}

	public static String toHex(byte[] bytes) {

	    final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
	    StringBuilder ret = new StringBuilder(bytes.length * 2);
	    for (int i=0; i<bytes.length; i++) {
	        ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
	        ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
	    }
	    return ret.toString();
	}
}


程序演示

一、开始运行界面
(1) 运行程序的时候,就已经生成了公钥和私钥,每运行一次就会得到不同的公私钥对,生成的公私钥对方便下面使用。
RSA数据摘要+数字签名(Java)

二、输入原文信息,开始提取原文信息的数据摘要
(1) 输入框输入原文信息
(2) 点击"提取数据摘要(MD5)"的选项
(3) 点击"开始"按钮,得到数据摘要
RSA数据摘要+数字签名(Java)

三、对数据摘要进行签名
(1) 步骤二已经得到了数据摘要
(2) 点击"RSA数据签名"选项,然后点击"开始按钮",使用上面得到的私钥对数据摘要进行签名。
RSA数据摘要+数字签名(Java)

四、对数据签名进行解签
(1) 选择"RSA数据签名"选项,然后点击"反解"按钮。反解过程就是使用上面得到的公钥对数据签名进行解签,得到数据摘要。
RSA数据摘要+数字签名(Java)

五、验证数据完整性
(1) 选择"提取数据摘要(MD5)"选项,然后点击"反解"按钮。这个步骤就是把原文信息重新用Hash生成数据摘要Q1,然后用Q1跟步骤四中得到的数据摘要Q2对比,如果Q1和Q2一样就输入一样的提示。如果不一致则提示原文信息被修改。
RSA数据摘要+数字签名(Java)


可能遇到的问题

return (new BASE64Encoder()).encodeBuffer(key);

这句代码可能会提示错误,因为Base64Encoder()方法是JDK8之前的方法,使用JDK8或者以上的则不能使用这个方法。
如果要使用这个方法,需要对项目配置进行修改。

选择你的项目–>JRE System Library–> Build Path–>Confugure Build Path

RSA数据摘要+数字签名(Java)

选择Libraries–> Access rules–>EditRSA数据摘要+数字签名(Java)

点击"Add"–>选择"Accessible"–>Rule Rattern写上"**",点击保存
RSA数据摘要+数字签名(Java)