为了保证数据安全,需要解决三个问题:机密性、完整性、身份验证(抗抵赖性)
-
机密性:传输内容非明文,即使数据被外界截获,也不能被他人解释或破解。
-
完整性:传输过程中内容不能够被篡改,若信息被篡改或不完整,接收方能够得知。
-
身份验证(抗抵赖性):接收方能够验证数据的实际发送方,确保数据不是被人“冒名顶替”而伪造的。
举例:
甲、乙双方军队攻打丙方,丙方比较强大,因此甲乙双方必须使用合理的配合战术,并且一起进攻,才能取胜,而甲乙双方不再一个地方,他们必须秘密通信。
当甲方军师研究好配合战术并确定了进攻时间,于是给乙方军师写了封信。现在问题来了,那这封信怎么才能保证安全?
1. 信中内容必须以加密的形式传输,只有乙方军师才能看得懂。否则万一这个信件被丙方所截获,那丙方就知道了甲乙双方的战术安排,这就是机密性。
密码本
2. 这个信件若真被丙方截获,丙方若想将计就计把信中内容偷偷修改,再发给乙方,那乙方必须知道这个信中途已被篡改,这就是完整性。
火漆封缄
3. 当乙方军师收到这个信件时,必须确认是甲方军师写的,而不是丙方伪造的信件,这就是身份验证。
印章或玉佩
机密性
机密性可通过加密算法保证,加密算法定义了明文、密文之间如何转换,也就是加解密的过程。加密算法分为:对称加密和非对称加密。
对称加密算法
对称加密指加密和解密使用相同密钥的加密算法。常见的对称加密算法主要有DES,AES,3DES、RC2、RC4和RC5等
非对称加密算法
非对称加密算法需要两个密钥:公开密钥(public key)和私有密钥(private key)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密; 反之也是如此。常见的非对称加密算法:RSA、DSA、ECC等。
public class RSAEncrypt {
final static Base64.Decoder decoder = Base64.getDecoder();
final static Base64.Encoder encoder = Base64.getEncoder();
/**
* RSA公钥加密
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt( String str, String publicKey ) throws Exception{
//base64编码的公钥
byte[] decoded = decoder.decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = encoder.encodeToString(cipher.doFinal(str
.getBytes("UTF-8")));
return outStr;
}
/**
* RSA私钥解密
* @param str 加密字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey) throws Exception{
//base64解码后的字符串
byte[] inputByte = decoder.decode(str.getBytes("UTF-8"));
//base64编码的私钥
byte[] decoded = decoder.decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
//生成密钥对
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
//可以理解为:加密后的密文长度,实际原文要小些 越大 加密解密越慢
keyGen.initialize(512);
KeyPair keyPair = keyGen.generateKeyPair();
return keyPair;
}
public static Map<Integer, String> genKeyPair() throws Exception {
Map<Integer, String> keyMap = new HashMap<Integer, String>();
//生成公私钥对
KeyPair keyPair = getKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
String publicKeyString = encoder.encodeToString(publicKey.getEncoded());
// 得到私钥字符串
String privateKeyString = encoder.encodeToString(privateKey.getEncoded());
// 将公钥和私钥保存到Map
keyMap.put(0,publicKeyString); //0表示公钥
keyMap.put(1,privateKeyString); //1表示私钥
return keyMap;
}
}
/**
* 测试RSA加解密
*/
@Test
public void test1() throws Exception {
Map<Integer, String> keyMap = RSAEncrypt.genKeyPair();
String content = "渔阳鼓";
System.out.println("随机生成的公钥为:" + keyMap.get(0));
System.out.println("随机生成的私钥为:" + keyMap.get(1));
String messageEn = RSAEncrypt.encrypt(content,keyMap.get(0));
System.out.println("加密后的字符串为:" + messageEn);
String messageDe = RSAEncrypt.decrypt(messageEn,keyMap.get(1));
System.out.println("解密后的字符串为:" + messageDe);
}
完整性
信息完整性可通过提取并对比消息摘要的方式来实现。消息摘要就是根据一定的运算规则对原始数据进行某种形式的信息提取,通过消息摘要后的消息摘要的长度总是固定的,它也叫做数据指纹,因为它可以唯一的标识一段数据。常见的摘要算法有:sha1、sha256、md5、crc32等。
SHA256摘要算法
public class SHA256 {
/**
* 实现SHA256加密
* @param str 加密后的报文
* @return
*/
public static String getSHA256(String str) {
MessageDigest messageDigest;
String encodestr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodestr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encodestr;
}
private static String byte2Hex(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i = 0; i < bytes.length; i++) {
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length() == 1) {
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
}
/**
* 测试SHA256摘要算法
*/
@Test
public void test2() throws Exception {
String content = "渔阳鼓";
System.out.println(content + ": 第一次摘要后的字符串为:" + SHA256.getSHA256(content));
System.out.println(content + ": 第二次摘要后的字符串为:" + SHA256.getSHA256(content));
}
身份验证
即使我们保证了数据的机密性和完整性,这里面仍然存在一些问题:
- 接收方若要验证消息完整性,必须得到发送方对消息产生的摘要,若第三方得知摘要算法,那摘要也是可以被伪造的,因此摘要本身也需要被加密。
- 消息发送来源如何确定,怎么确定不是第三方伪造的?
发送方将消息原文使用摘要算法生成摘要,再用私钥对摘要进行加密,生成数字签名,然后将内容附上数字签名一起传输。
接收方收到消息后,用发送方的公钥对数字签名进行解密(能解密成功就完成了对发送方的身份验证),得到摘要A,然后再对原文使用摘要算法生成摘要B,比对摘要A和B是否相同,相同则说明内容没有被篡改。
使用SHA1作为摘要算法,使用RSA作为签名加密算法的签名及验签过程
public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
/**
* RSA签名
* @param content 待签名数据
* @param privateKey 私钥
* @param input_charset 编码格式
* @return 签名值
*/
public static String sign(String content, String privateKey, String input_charset) {
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(decoder.decode(privateKey));
KeyFactory keyf = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
signature.initSign(priKey);
signature.update(content.getBytes(input_charset));
byte[] signed = signature.sign();
return encoder.encodeToString(signed);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* RSA验签名检查
* @param content 待签名数据
* @param sign 签名值
* @param public_key 公钥
* @param input_charset 编码格式
* @return 布尔值
*/
public static boolean verify(String content, String sign, String public_key, String input_charset) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = decoder.decode(public_key);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
signature.initVerify(pubKey);
signature.update(content.getBytes(input_charset));
boolean bverify = signature.verify(decoder.decode(sign));
return bverify;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 测试SHA1WithRSA签名、验证签名
*/
@Test
public void test3() throws Exception {
Map<Integer, String> akeyMap = RSAEncrypt.genKeyPair();//a方密钥对
System.out.println("随机生成的a方公钥为:" + akeyMap.get(0));
System.out.println("随机生成的a方私钥为:" + akeyMap.get(1));
String content = "传智播客";
System.out.println("------------a向b发送数据,使用a的私钥生成签名-----------");
String signature = RSAEncrypt.sign(content, akeyMap.get(1), "utf-8");
System.out.println("原文:'" +content+ "'生成签名:" + signature);
System.out.println("----------b接收到a发的数据,使用a的公钥验证签名-----------");
if (RSAEncrypt.verify(content, signature, akeyMap.get(0), "utf-8")) {
System.out.println("验证签名成功:" + signature);
} else {
System.out.println("验证签名失败!");
}
}