概述
在https://blog.csdn.net/chinoukin/article/details/100934995章節中,我介紹了用Hmac算法用於簽名算法中的方法,本章節中將對常見的簽名算法“SHA256withRSA”做一下分析與介紹。
此文由來
當我在使用springcloud的oauth2時,經常會用到jwt作爲用戶身份的確認和鑑權。而我們知道jwt是由三部分組成,其中第三部分就是數字簽名了,而springcloud的oauth2中的默認jwt簽名算法爲SHA256withRSA。
下面通過調用OauthServer的{/oauth/token_key}接口來看一下返回情況:
HMAC與SHA256withRSA
hmac做簽名時需要指定一個secret和指定hamc算法,常見的hmac算法有hamcsha1、hamcsha256等,通常用hmac生成信息摘要後會再用RSA算法對其進行加密
SHA256withRSA做簽名則一步到位,需要先生成RSA密鑰對,其中私鑰用於簽名,公鑰用於驗籤。
爲了方便理解:
hmac數字簽名 = rsa_encrypt(hmac(信息) + RSA私鑰)
SHA256withRSA數字簽名 = SHA256withRSA_encrypt(信息 + RSA私鑰)
RSA密鑰之PKCS#1、PKCS#8
pkcs時密鑰的語法標準,細心的同學應該會經常遇到PKCS#1,PKCS#8,PKCS#12等標準,具體所代表的含義這裏不做贅述了。通常我們生成密鑰對時,會對私鑰進行加密,加密後的格式爲PKCS#1或PKCS#8格式,而一般不會對公鑰進行加密。簡單例子: 用openssl生成PKCS#1格式的私鑰:openssl genrsa -des3 -out test.key 1024
通過文件首尾部能判斷:
JAVA示例
1.簽名示例
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.util.encoders.Base64;
import java.io.IOException;
import java.io.StringWriter;
import java.security.InvalidKeyException;
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.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
public class SignTest {
/**
* 生成沒有加密的密鑰對
* @param algorithm
* @param keysize
* @return
* @throws Exception
*/
public static Map<String, Object> createKey(String algorithm,int keysize) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm);
keyPairGen.initialize(keysize);
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("publicKey", publicKey);
keyMap.put("privateKey", privateKey);
return keyMap;
}
/**
* 生成加過密的密鑰對 pkcs#1格式私鑰 pkcs#8格式公鑰
*
* @param algorithm
* @param keysize
* @param privateKeyPwd
* @return
* @throws Exception
*/
public static Map<String, Object> createEncryKeyStr(String algorithm, int keysize, String privateKeyPwd) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm);
keyPairGen.initialize(keysize);
KeyPair keyPair = keyPairGen.generateKeyPair();
//通過對象 KeyPair 獲取RSA公私鑰對象RSAPublicKey RSAPrivateKey
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
String pubKeyStr = new String(Base64.encode(publicKey.getEncoded())); //pkcs8格式
String priKeyStr = new String(Base64.encode(privateKey.getEncoded())); //pkcs8格式
System.out.println(priKeyStr);//從輸出結果可以知道是PKCS#8格式的
//私鑰加密
String privateKeyStr = privateKeyPwdToPKCS1(privateKey, privateKeyPwd);//使用BC加密私鑰格式會被轉爲PKSC#1格式
System.out.println(privateKeyStr);
System.out.println(privateKeyNoPwdToPKCS1(privateKey));
//公私鑰對象存入map中
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put("publicKeyStr", pubKeyStr);
keyMap.put("privateKeyStr", privateKeyStr);
return keyMap;
}
/**
* 將私鑰轉爲PKCS#1格式私鑰(加密)
*
* @param privateKey
* @param filePasswd
* @return
*/
private static String privateKeyPwdToPKCS1(PrivateKey privateKey, String filePasswd) {
Security.addProvider(new BouncyCastleProvider());
StringWriter sw = new StringWriter();
PEMWriter writer = new PEMWriter(sw);
try {
writer.writeObject(privateKey, "DESEDE", filePasswd.toCharArray(),
new SecureRandom());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return sw.toString();
}
/**
* 將私鑰轉爲PKCS#1格式私鑰(沒有加密)
* @param privateKey
* @return
*/
private static String privateKeyNoPwdToPKCS1(PrivateKey privateKey){
Security.addProvider(new BouncyCastleProvider());
StringWriter sw = new StringWriter();
PEMWriter writer = new PEMWriter(sw);
try {
writer.writeObject(privateKey);
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(writer !=null){
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return sw.toString();
}
public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
public static final String ENCODE_ALGORITHM = "SHA-256";
public static final String PLAIN_TEXT = "test string";
/**
* 簽名
* @param privateKey 私鑰
* @param plainText 明文
* @return
*/
public static byte[] sign(PrivateKey privateKey, String plainText) {
MessageDigest messageDigest;
byte[] signed = null;
try {
messageDigest = MessageDigest.getInstance(ENCODE_ALGORITHM);
messageDigest.update(plainText.getBytes());
byte[] outputDigest_sign = messageDigest.digest();
System.out.println("SHA-256編碼後-----》"
+ org.apache.commons.codec.binary.Base64.encodeBase64String(outputDigest_sign));
Signature Sign = Signature.getInstance(SIGNATURE_ALGORITHM);
Sign.initSign(privateKey);
Sign.update(outputDigest_sign);
signed = Sign.sign();
System.out.println("SHA256withRSA簽名後-----》"
+ org.apache.commons.codec.binary.Base64.encodeBase64String(signed));
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
e.printStackTrace();
}
return signed;
}
/**
* 驗籤
* @param publicKey 公鑰
* @param plain_text 明文
* @param signed 簽名
*/
public static boolean verifySign(PublicKey publicKey, String plain_text, byte[] signed) {
MessageDigest messageDigest;
boolean SignedSuccess=false;
try {
messageDigest = MessageDigest.getInstance(ENCODE_ALGORITHM);
messageDigest.update(plain_text.getBytes());
byte[] outputDigest_verify = messageDigest.digest();
//System.out.println("SHA-256加密後-----》" +bytesToHexString(outputDigest_verify));
Signature verifySign = Signature.getInstance(SIGNATURE_ALGORITHM);
verifySign.initVerify(publicKey);
verifySign.update(outputDigest_verify);
SignedSuccess = verifySign.verify(signed);
System.out.println("驗證成功?---" + SignedSuccess);
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
e.printStackTrace();
}
return SignedSuccess;
}
/**
* bytes[]換成16進制字符串
*
* @param src
* @return
*/
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
public static void main(String[] args) throws Exception {
Map<String, Object> keyMap = createKey("RSA", 2048);
PublicKey publicKey = (PublicKey) keyMap.get("publicKey");
PrivateKey privateKey = (PrivateKey) keyMap.get("privateKey");
byte[] signBytes = sign(privateKey, PLAIN_TEXT);
System.out.println(verifySign(publicKey, PLAIN_TEXT, signBytes));
}
}
2.簽發JWT示例
import com.cyq.CheckResult;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* maven依賴
*
* <dependency>
* <groupId>io.jsonwebtoken</groupId>
* <artifactId>jjwt</artifactId>
* <version>0.9.0</version>
* </dependency>
*/
/**
* @author cyq
*/
public class JwtUtils {
public static void main(String[] args) throws Exception {
// 簽發jwt(簽名算法hmac256)
System.out.println("簽發jwt(簽名算法hmac256):");
String jwt = createJWT("123", "jwt_hmac256", 60);
System.out.println(jwt);
// 解析
SecretKey secretKey = generalKey();
Claims claims = parseJWT(jwt, secretKey);
System.out.println(claims);
// 簽發jwt(簽名算法SHA256withRSA)
System.out.println("\n簽發jwt(簽名算法SHA256withRSA):");
Map<String, Object> keyMaps = createRSAKey(2048);
String jwt2 = createJWT2("456", "jwt_SHA256withRSA", 60, (RSAPrivateKey) keyMaps.get("privateKey"));
System.out.println(jwt2);
// 解析
Claims claims2 = parseJWT(jwt2, (RSAPublicKey) keyMaps.get("publicKey"));
System.out.println(claims2);
}
/**
* 簽發JWT
* @param id
* @param subject 可以是JSON數據 儘可能少
* @param ttlMillis
* @return String
*
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;// HmacSha256
OffsetDateTime now = OffsetDateTime.now(ZoneId.of("Asia/Shanghai"));
//Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject) // 主題
.setIssuer("user") // 簽發者
.setIssuedAt(Date.from(now.toInstant())) // 簽發時間
.signWith(signatureAlgorithm, secretKey); // 簽名算法以及密匙
if (ttlMillis >= 0) {
OffsetDateTime expTime = now.plusSeconds(ttlMillis);
builder.setExpiration(Date.from(expTime.toInstant())); // 過期時間
}
return builder.compact();
}
/**
* 簽發JWT
* @param id
* @param subject 可以是JSON數據 儘可能少
* @param ttlMillis
* @return String
*
*/
public static String createJWT2(String id, String subject, long ttlMillis, RSAPrivateKey privateKey) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256;// SHA256withRSA
OffsetDateTime now = OffsetDateTime.now(ZoneId.of("Asia/Shanghai"));
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject) // 主題
.setIssuer("user") // 簽發者
.setIssuedAt(Date.from(now.toInstant())) // 簽發時間
.signWith(signatureAlgorithm, privateKey); // 簽名算法以及密匙
if (ttlMillis >= 0) {
OffsetDateTime expTime = now.plusSeconds(ttlMillis);
builder.setExpiration(Date.from(expTime.toInstant())); // 過期時間
}
return builder.compact();
}
/**
* 生成沒有加密的密鑰對
* @param keysize
* @return
* @throws Exception
*/
public static Map<String, Object> createRSAKey(int keysize) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("rsa");
keyPairGen.initialize(keysize);
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("publicKey", publicKey);
keyMap.put("privateKey", privateKey);
return keyMap;
}
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decode("testjwt");
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
*
* 解析JWT字符串
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt, Key key) throws Exception {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwt)
.getBody();
}
/**
* 驗證JWT
* @param jwtStr
* @return
*/
public static CheckResult validateJWT(String jwtStr, Key key) {
CheckResult checkResult = new CheckResult();
Claims claims = null;
try {
claims = parseJWT(jwtStr, key);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
e.printStackTrace();
checkResult.setErrCode("JWT_ERRCODE_EXPIRE");
checkResult.setSuccess(false);
} catch (SignatureException e) {
e.printStackTrace();
checkResult.setErrCode("JWT_ERRCODE_FAIL");
checkResult.setSuccess(false);
} catch (Exception e) {
e.printStackTrace();
checkResult.setErrCode("JWT_ERRCODE_FAIL");
checkResult.setSuccess(false);
}
return checkResult;
}
}