加密技术是最常见的安全保密手段,数据加密技术的关键在于加密/解密算法和密钥管理。数据加密的基本过程是对原来为明文的文件或数据按某种加密算法进行处理,使其成为不可读的一段代码,通常称为“密文”。“密文”只能在输入相应的密钥之后才能显示出原来的内容,通过这样的途径保证被加密的内容不被窃取。
对称加密的特点是文件或数据加密和解密使用相同的密钥,这种方法在密码学中称为对称加密算法。
非对称加密算法区别于对称加密算法,非对称加密算法需要两个密钥,公钥(Publickey)和私钥(PrivateKey)。公钥与私钥是一对,如果使用公钥对数据加密,需要对应的私钥才能解密;如果使用私钥对数据加密,需要对应的公钥才能解密。
非对称加密有两种用法。第一种用法是公钥加密、私钥解密——用于加密;第二种用法是私钥签名、公钥验签——用于签名。
什么时候用公钥,什么时候用私钥是经常被问起的问题。
第一种情况,当用于加密时,肯定是不想让别人知道我加密的内容,此时用于解密的肯定是私钥。比如A需要B发送一个加密后的数据过来,B知道A的公钥,C也知道A的公钥,A将数据使用A的公钥加密后发送出去,A获得了加密后的数据并用A的私钥解密,而如果C也想获取这一个数据,但是C没有A的私钥也是获取不了的。
第二种情况,用于签名。这里签名的意思是证明这个数据时由“我”发出的,要证明这一个数据由“我”发出,那肯定要用私钥加密,也就是签名,其他人获取这一加密数据后用“我”的公钥解密,也就是校验这一个签名。这里举一个例子,在一个“商城系统”微服务中,客户端请求用户认证微服务,获取到了一个私钥加密的token,这个token就是“商城系统”的签名,拿到这一个token的客户端携带token访问“商城系统”的其他微服务,其他微服务对这个客户端的请求进行拦截并使用“商城系统”的公钥校验token,如果通过验证才允许请求继续进行。
RSA算法是常用的非对称加密技术,名称的由来是提出人Ron Rivest、Adi Shamir、Leonard Adleman三个人的姓氏的首字母。下面的内容详细记录了采用RSA算法进行私钥签名、公钥验签。
首先采用RSA算法生成证书文件,这个证书文件中会包含公钥与私钥的信息。
Keytool是Java提供的一个证书管理工具,这里我们就用keytool来生成密钥证书,步骤如下:
1.保证JDK的环境变量是可用的;
2.打开cmd窗口,并进入证书生成的目标文件夹(证书会在当前文件夹中生成);
3.执行代码段中的命令,命令的各个参数意义如下:
-alias:密钥的别名,这里我设置为mountainkey
-keyalg:使用的hash算法,这里我使用的是RSA算法
-keypass:密钥的访问密码,这里我设置为mountain
-keystore:密钥库的文件名,这里我设置为mountain.keystore,生成文件后会在mountain.keystore中保存证书内容
-storepass:密钥库的访问密码,这里我设置为mountainkeystore
keytool -genkeypair -alias mountainkey -keyalg RSA -keypass mountain -keystore mountain.keystore -storepass mountainkeystore
命令的输入过程如下图所示。
执行成功会在dos窗口所处的目录中生成证书文件,如下图所示。
生成的证书文件mountain.keystore中包含了公钥与私钥,现在从证书中获取公钥。这里需要用到openssl工具包,下载地址为http://slproweb.com/products/Win32OpenSSL.html
下载并安装成功后,将openssl的bin目录加入path环境变量,如下图所示。
现在,以cmd进入刚刚证书文件所在的目录,输入以下命令(证书文件名为mountain.keystore),在提示输入秘钥库密码后输入storepass设置的内容,也就是秘钥库密码。
keytool -list -rfc --keystore mountain.keystore | openssl x509 -inform pem -pubkey
显示的内容如下,该内容包含了公钥的信息,也就是从BEGIN PUBLIC KEY到END PUBLIC KEY的内容,该字符串用于去校验通过私钥加密后的内容。
现在测试通过获取证书当中的私钥对数据进行加密,能够读取证书文件并获取私钥的方法有很多,这里我使用Spring Security所提供的方法来对进行此项操作。
/**
* 读取证书文件中的私钥,并使用私钥对数据加密,得到加密后的字符串
*/
@Test
public void testGenerateToken(){
String keystore = "mountain.keystore";
String keystorePassword = "mountainkeystore";
ClassPathResource classPathResource = new ClassPathResource(keystore);
String alias = "mountainkey";
String keyPassword = "mountain";
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,keystorePassword.toCharArray());
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, keyPassword.toCharArray());
RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
Map contentMap = new HashMap<>();
contentMap.put("hello","hello world");
String bodyString = JSON.toJSONString(contentMap);
Jwt jwt = JwtHelper.encode(bodyString, new RsaSigner(aPrivate));
String encodedContent = jwt.getEncoded();
System.out.println(encodedContent);
}
控制台打印得到的私钥加密后的内容如下。
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6ImhlbGxvIHdvcmxkIn0.LNB7GpiTYl1G4MNYpFDPgay6funMLXeoGUo3TU30IfhIJiSU7zobJ-SpJap6ARbCJoRc5_JURb4ti-6vanf3cpIyOayk2H83gfBOSY8_wACbpfhlt_PGT0RYfStGC7OVwKFUAdEaO6hYYD466vI8d6d9uNb1RfNfGToYlosH05McYAyrM2XLcy0T7glZmuNw_JgMflRAjN09K13MlQZttn6W-lhSlv38CE1_CJ8SzNLHC6U7Dzd1FlcW9Xs-IvDalw1xzgzNRfbNpdvcZTnNhbdNa6_bSlSIfSM9IHngG3EVS_hwQkGOfBeV5gxQZrW4BB9niK8FwvoDB1cVjy4Ktw
将使用私钥签名后的数据进行校验,这里依然使用Spring Security提供的方法。
/**
* 使用公钥校验加密后的字符串
*/
@Test
public void testCheckTokenByPublicKey() {
// 公钥
String publickey = "-----BEGIN PUBLIC KEY-----" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbuddIbMU5FjqpJR4Ikn" +
"xktq1k/0C10XfOR2VU79qh4PXGSNn6Vt5BZgK8Ow4cA7SzAMoBUkxev/5I2Mx4p4" +
"gk+6ImQ+IsTi6tqXOQ7DHjpogfsX/VeXJ93Aeq8v9hOqtKYj5q1jy4skGRvbD+c8" +
"Z6knxLQb9I6HE39v3BZZL+WTYz6kx8BTZ0rPd7C5uOVqYo/FG+QzY+Ndv2u7gNcy" +
"V9sRnM+hI2w5e87LuG+V6GhekdKqtS0dsjKskpjX/L2ppykdi1hkCtS/ipZ5aaAj" +
"/SzVfWfQTxw4Yh+3QVc+KoSW61KlCZ+SSu7YrszAqlg93927/eWWLjYUFsCqP0jw" +
"5wIDAQAB" +
"-----END PUBLIC KEY-----";
// 私钥加密后的内容
String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6ImhlbGxvIHdvcmxkIn0.LNB7GpiTYl1G4MNYpFDPgay6funMLXeoGUo3TU30IfhIJiSU7zobJ-SpJap6ARbCJoRc5_JURb4ti-6vanf3cpIyOayk2H83gfBOSY8_wACbpfhlt_PGT0RYfStGC7OVwKFUAdEaO6hYYD466vI8d6d9uNb1RfNfGToYlosH05McYAyrM2XLcy0T7glZmuNw_JgMflRAjN09K13MlQZttn6W-lhSlv38CE1_CJ8SzNLHC6U7Dzd1FlcW9Xs-IvDalw1xzgzNRfbNpdvcZTnNhbdNa6_bSlSIfSM9IHngG3EVS_hwQkGOfBeV5gxQZrW4BB9niK8FwvoDB1cVjy4Ktw";
//校验jwt令牌
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey));
//拿到jwt令牌中自定义的内容
String claims = jwt.getClaims();
System.out.println(claims);
}
控制台打印的内容与加密前的数据是一致的,表示这个数据是正确。
{"hello":"hello world"}