RSA数据摘要+数字签名(Java)
背景
之前我一直不明白,比如A、B双方加密通信的时候,为什么A要使用B的公钥来加密,为A什么不使用A的私钥加密,然后B用A的公钥解密不就可以了吗?
其实,A的私钥主要是A用来签名的,签名顾名思义是表明这个东西是A发送的,而不是别人发过来的。为什么A用自己的私钥签名就说明这个东西就是A发过来的呢?因为用B用A的公钥解签的时候,得到了原文的数据摘要,然后B根据得到的明文信息然后使用Hash函数重新得到数据摘要,两个数据摘要一对比,如果一致,那么就是A发过来的,表明了即使A发过来的且原文信息没有被改动过。
具体流程图下图所示:
目标
使用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) 运行程序的时候,就已经生成了公钥和私钥,每运行一次就会得到不同的公私钥对,生成的公私钥对方便下面使用。
二、输入原文信息,开始提取原文信息的数据摘要
(1) 输入框输入原文信息
(2) 点击"提取数据摘要(MD5)"的选项
(3) 点击"开始"按钮,得到数据摘要
三、对数据摘要进行签名
(1) 步骤二已经得到了数据摘要
(2) 点击"RSA数据签名"选项,然后点击"开始按钮",使用上面得到的私钥对数据摘要进行签名。
四、对数据签名进行解签
(1) 选择"RSA数据签名"选项,然后点击"反解"按钮。反解过程就是使用上面得到的公钥对数据签名进行解签,得到数据摘要。
五、验证数据完整性
(1) 选择"提取数据摘要(MD5)"选项,然后点击"反解"按钮。这个步骤就是把原文信息重新用Hash生成数据摘要Q1,然后用Q1跟步骤四中得到的数据摘要Q2对比,如果Q1和Q2一样就输入一样的提示。如果不一致则提示原文信息被修改。
可能遇到的问题
return (new BASE64Encoder()).encodeBuffer(key);
这句代码可能会提示错误,因为Base64Encoder()方法是JDK8之前的方法,使用JDK8或者以上的则不能使用这个方法。
如果要使用这个方法,需要对项目配置进行修改。
选择你的项目–>JRE System Library–> Build Path–>Confugure Build Path
选择Libraries–> Access rules–>Edit
点击"Add"–>选择"Accessible"–>Rule Rattern写上"**",点击保存