区块链技术不是一个新发明的技术,而是一个集成了多方面基础技术的综合性技术系统,是几个之前就有的基础技术的优雅组合而成。我认为,其中有四项必不可缺的核心技术,分别是:共识机制、密码学原理、链式哈希结构和分布式数据存储(多节点)。
公钥、私钥这种非对称数字加密技术实现交易双方的互相信任。非对称加密技术是区块链技术体系很重要的一部分。
公钥私钥的原则:
- 一个公钥对应一个私钥,公钥私钥成对出现。
- 密钥对中,让大家都知道的是公钥,不告诉大家,只有自己知道的,是私钥。
- 公钥用来加密和验证数字签名;私钥用来解密和生成数字签名。生成数字签名本质上是用私钥进行加密,验证数字签名是用公钥对私钥加密密文进行解密。可以归纳为:如果用其中一个密钥加密数据,则只有对应的那个密钥才可以解密。
- 如果用其中一个密钥可以进行解密数据,则该数据必然是对应的那个密钥进行的加密。
为什么用非对称加密?
一个网站要对传输进行加密,如果用对称加密,则有以下几种情况:
1 每个访问用户的秘钥相同。这种情况,服务器只要保存一个秘钥。除非你的网站用户是特定的内部用户,否则用户拿到秘钥就能对密文进行解密。
2 每个访问用户的秘钥不一样。这种情况,服务器需要保存大量秘钥。如果网站有上亿用户,则需要存储上亿的秘钥,维护成本巨大。
如果是非对称加密,网站只需要保存自己的私钥,用户可以随意下载网站公钥。
数字签名:就是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。数字签名了的文件的完整性是很容易验证的
流程:
(1) 被发送文件用
密码散列函数(MD5,SHA,SM3)产生的
摘要
(2) 发送方用自己的私用密钥对摘要再加密,这就形成了数字签名。
(3) 将原文和加密的摘要同时传给对方。
公共密钥对摘要解密,获取发送方生成的摘要,同时对收到的文件用SHA编码加密产生又一 摘要。
摘要和收到的文件在接收方重新加密产生的 摘要相互对比。如两者一致,则说明传送过程中信息没有被破坏或篡改过。否则不然。
数字签名,可以保证收到的文件没有被篡改,也可以保证发送者的身份。因为私钥生产了数字签名,私钥是不公开的。
说了这么多,还是写段代码来试试看。
/**
* Bestpay.com.cn Inc.
* Copyright (c) 2011-2018 All Rights Reserved.
*/
package rsa;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
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.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
/**
*
* @author huyajun
* @version $Id: RSAsecurityTest.java, v 0.1 2018年3月14日 下午6:09:53 huyajun Exp $
*/
public class RSAsecurityTest {
public static String PUBLIC_KEY = "pub_key";
public static String PRIVATE_KEY = "pri_key";
/**
* BASE64解密
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) {
return Base64.getDecoder().decode(key);
}
/**
* BASE64加密
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) {
return Base64.getEncoder().encodeToString(key);
}
/**
* 初始化密钥对
*
* @return
*/
public static Map<String, String> initRsaKey() {
//1.初始化秘钥
KeyPairGenerator keyPairGenerator;
try {
//1.初始化秘钥
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
//秘钥长度
keyPairGenerator.initialize(512);
//初始化秘钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//公钥
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
//私钥
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, String> keyMap = new HashMap<String, String>(2);
keyMap.put(PUBLIC_KEY, encryptBASE64(rsaPublicKey.getEncoded()));
keyMap.put(PRIVATE_KEY, encryptBASE64(rsaPrivateKey.getEncoded()));
return keyMap;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
///
/**
* 公钥处理,返回base64编码的字符串
* @param file
* @param rsaPublicKeyStr
* @param model 加密:Cipher.ENCRYPT_MODE;解密:Cipher.DECRYPT_MODE
* @return 公钥处理后的字符串,base64编码
*/
public static String publicKeyDeal(String file, String rsaPublicKeyStr, int model) {
try {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
decryptBASE64(rsaPublicKeyStr));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(model, publicKey);
byte[] result = cipher.doFinal(decryptBASE64(file));
// 必须用base64 进行编解码
return encryptBASE64(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 私钥 处理,返回base64编码的字符串
* @param file
* @param rsaPrivateKeyStr
* @param model 加密:Cipher.ENCRYPT_MODE;解密:Cipher.DECRYPT_MODE
* @return 私钥处理后的字符串,base64编码
*/
public static String privateKeyDeal(String file, String rsaPrivateKeyStr, int model) {
try {
//生成私钥
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
decryptBASE64(rsaPrivateKeyStr));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
//初始化加密
cipher.init(model, privateKey);
byte[] result = cipher.doFinal(decryptBASE64(file));
//不能返回, new String(result) ,会出现乱码,导致没法解码
// 必须用base64 进行编解码
return encryptBASE64(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String EncoderByMd5(String str) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] result = md5.digest(str.getBytes("utf-8"));
return toHex(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private 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();
}
public static void main(String[] args) {
Map<String, String> keyMap = initRsaKey();
String file = "甜橙金融,互联网金融行业第三";
// 公钥加密,私钥解密,常规用法;
if (keyMap != null) {
String str = publicKeyDeal(encryptBASE64(file.getBytes()), keyMap.get(PUBLIC_KEY),
Cipher.ENCRYPT_MODE);
System.out.println("公钥对明文加密:" + str);
String str2 = privateKeyDeal(str, keyMap.get(PRIVATE_KEY), Cipher.DECRYPT_MODE);
System.out.println("私钥对密文解密:" + new String(decryptBASE64(str2)));
}
System.out.println("");
// 私钥加密,公钥解密,得到明文;-- 数字签名 用这个道理
if (keyMap != null) {
String str = privateKeyDeal(encryptBASE64(file.getBytes()), keyMap.get(PRIVATE_KEY),
Cipher.ENCRYPT_MODE);
System.out.println("私钥对明文加密:" + str);
String str2 = publicKeyDeal(str, keyMap.get(PUBLIC_KEY), Cipher.DECRYPT_MODE);
System.out.println("公钥对密文解密:" + new String(decryptBASE64(str2)));
}
System.out.println("");
/
// 数字签名
// 生产摘要
String md5Str = EncoderByMd5(file);
System.out.println("发送方生成摘要:" + md5Str);
//2用私钥 对摘要进行加密
String signStr = privateKeyDeal(encryptBASE64(md5Str.getBytes()), keyMap.get(PRIVATE_KEY),
Cipher.ENCRYPT_MODE);
System.out.println("发送方用私钥对摘要加密:" + signStr);
//3 发送原文+摘要密文(数字签名) 给接收方
System.out.println("接收方收到原文和摘要");
//4 接收方用公钥解密摘要
String md5Str_Decrypt = new String(decryptBASE64(publicKeyDeal(signStr,
keyMap.get(PUBLIC_KEY), Cipher.DECRYPT_MODE)));
System.out.println("接收方用公钥对摘要解密:" + md5Str_Decrypt);
//5 用同样的算法对原文生成摘要
String md5Str2 = EncoderByMd5(file);
System.out.println("接收方生成的摘要:" + md5Str2);
//6 对比公钥解密的摘要和用原文生成的摘要
if (md5Str2.equals(md5Str_Decrypt)) {
System.out.println("数字签名验证成功!");
} else {
System.out.println("数字签名验证失败!");
}
}
}
代码可以看出来:用公钥加密后,用对应的私钥可以解密;反过来,私钥加密后,用对应的公钥也可以解密。
整个数字签名的流程稍微有点复杂。Java 还提供了专门的签名类,省去了这些繁琐的步骤。
代码如下:
/**
* Bestpay.com.cn Inc.
* Copyright (c) 2011-2018 All Rights Reserved.
*/
package rsa;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author huyajun
* @version $Id: SignTest.java, v 0.1 2018年4月22日 下午7:29:08 huyajun Exp $
*/
public class SignTest {
private static final String PUBLIC_KEY = "PUBLIC_KEY";
private static final String PRIVATE_KEY = "PRIVATE_KEY";
/**
*
* @param args
*/
public static void main(String[] args) {
Map<String, String> keyMap = initRsaKey();
String content = "123";
String sign = null;
try {
sign = sign(content.getBytes(), keyMap.get(PRIVATE_KEY));
System.out.println("私钥签名结果:" + sign);
} catch (Exception e) {
e.printStackTrace();
}
//5.公钥验签
try {
boolean flag = verify(content.getBytes(), keyMap.get(PUBLIC_KEY), sign);
System.out.println("公钥验证数字签名:" + flag);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* BASE64解密
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) {
return Base64.getDecoder().decode(key);
}
/**
* BASE64加密
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) {
return Base64.getEncoder().encodeToString(key);
}
/**
* 初始化密钥对
*
* @return
*/
public static Map<String, String> initRsaKey() {
//1.初始化秘钥
KeyPairGenerator keyPairGenerator;
try {
//1.初始化秘钥
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
//秘钥长度
keyPairGenerator.initialize(512);
//初始化秘钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//公钥
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
//私钥
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, String> keyMap = new HashMap<String, String>(2);
keyMap.put(PUBLIC_KEY, encryptBASE64(rsaPublicKey.getEncoded()));
keyMap.put(PRIVATE_KEY, encryptBASE64(rsaPrivateKey.getEncoded()));
return keyMap;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/**
* 私钥签名
*
* @param data 原文件
* @param privateKey
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
//构造PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decryptBASE64(privateKey));
//指定加密算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//取私钥匙对象
PrivateKey privateKeyObj = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
//用私钥对信息生成数字签名
Signature signature = Signature.getInstance("MD5withRSA");// MD2withRSA SHA1withRSA MD5withRSA
signature.initSign(privateKeyObj);
signature.update(data);
return encryptBASE64(signature.sign());
}
/**
* 校验数字签名
* @param data 加密数据
* @param publicKey 公钥
* @param sign 数字签名
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
//构造X509EncodedKeySpec对象
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decryptBASE64(publicKey));
//指定加密算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//生成公钥匙对象
PublicKey publicKeyObj = keyFactory.generatePublic(x509EncodedKeySpec);
Signature signature = Signature.getInstance("MD5withRSA");// MD2withRSA SHA1withRSA MD5withRSA
signature.initVerify(publicKeyObj);
signature.update(data);
//验证签名是否正常
return signature.verify(decryptBASE64(sign));
}
}
Signature 类帮我们实现了生成数字签名和校验数字签名的方法,直接用,比自己去实现方便的多。
Signature.getInstance("MD5withRSA"); 表示用MD5做摘要,用RSA做加解密。同理你还可以选择SHA1withRSA 。 注意生成签名和验签的方法要相同。
今天就讲到这里,下一篇文章,我们介绍区块链知识点之--共识算法。敬请期待