深入理解簽名驗籤
數字簽名中,包含了兩個過程:
1.對要簽名的資訊,用指定的hash演算法,獲取資訊的hash值.
2.用私鑰,對hash值進行加密,輸出加密串(也就是簽名值).
以上方式也就是裸簽名,PKCS#1
驗證簽名:
1.對要簽名的資訊(也就是簽名原文),用指定的hash演算法,獲取資訊的hash值.
2.用公鑰資訊,解密簽名值,從中獲取加密的hash串,和上面獲取的hash值進行對比,一致則認為驗籤通過,不一致則不通過.
需要主要,如果呼叫遠端簽名,因為根據要簽名的資料格式的不同,所以我們本地驗籤的時候,也要根據不同的簽名方式,來進行驗證(也就說,他們那邊簽名的時候,真正用來簽名的位元組陣列是怎麼來的)
目前常見的幾種方式:(P1簽名驗籤)
contentType==CT_MESSAGE時,為待簽名的訊息;
contentType==CT_BASE64_DATA時,為待簽名的base64編碼資料;
contentType==CT_HASH時為待簽名的HASH;
contentType==CT_FILE_URL時為待簽名檔案地址URL
contentType== CT_STORAGE時為待簽名內容儲存編號
CT_HASH:告訴簽名方,我這個是對簽名原文hash過了的hash串,你們直接對這個值進行加密即可,不需要再hash了.
所以這種方式,我們本地驗籤的時候,要將原文放入進行驗籤,而不是hash過後的hash值(因為驗籤的時候,它又會hash一次)
/**
* 測試hash方式的簽名驗籤
* @throws Exception
*/
@Test
public void testSignWithHash() {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate usercert = cf.generateCertificate(new FileInputStream(USER_CERT));
PublicKey publicKey = usercert.getPublicKey ();
//讀取pfx證書上的祕鑰對資訊
BouncyCastleProvider provider = new BouncyCastleProvider();
Signature signature = Signature.getInstance("SHA1withRSA");
MessageDigest digest = MessageDigest.getInstance("SHA-1", provider);
List<UserInfo> userInfoList = userInfoService.findAll();
UserInfo userInfo = userInfoList.get(0);
Map<String, Object> puserCert = new HashMap<>();
puserCert.put("userInfo", userInfo);
List<UserCert> userCertList = userCertService.getListByParam(puserCert);
UserCert userCert = userCertList.get(0);
YuanZi_P1SignRequest yuanZiP1SignRequest = new YuanZi_P1SignRequest();
yuanZiP1SignRequest.setAlias(userInfo.getAlias());
yuanZiP1SignRequest.setHashAlg("SHA1");
yuanZiP1SignRequest.setContentType("CT_HASH");
String content = "我是簽名原文";
byte[] dataToSign = digest.digest(content.getBytes(Charset.forName("UTF-8")));
System.out.println("dataToSign:"+Base64.encodeBytes(dataToSign));
yuanZiP1SignRequest.setContent(Base64.encodeBytes(dataToSign));
YuanZi_P1SignResponse yuanZiP1SignResponse = yuanZiService.signP1(yuanZiP1SignRequest);
System.out.println("原子服務返回的簽名值:"+yuanZiP1SignResponse.getSignedData());
byte[] dataHadSignature = Base64.decode(yuanZiP1SignResponse.getSignedData());
YuanZi_VerifyP1SignRequest yuanZiVerifyP1SignRequest = new YuanZi_VerifyP1SignRequest();
yuanZiVerifyP1SignRequest.setSignedData(yuanZiP1SignResponse.getSignedData());
yuanZiVerifyP1SignRequest.setContentType("CT_HASH");
yuanZiVerifyP1SignRequest.setContent(Base64.encodeBytes(dataToSign));
yuanZiVerifyP1SignRequest.setHashAlg("SHA1");
yuanZiVerifyP1SignRequest.setCert(userCert.getCertBuf().getCertsignBuf());
YuanZi_VerifyP1SignResponse yuanZiVerifyP1SignResponse = yuanZiService.verifyP1Sign(yuanZiVerifyP1SignRequest);
System.out.println(yuanZiVerifyP1SignResponse.getCode());
//本地驗籤
boolean verify=false;
signature.initVerify(usercert); //使用公鑰初始化簽名物件,用於驗證簽名
signature.update(content.getBytes(Charset.forName("UTF-8")));
verify= signature.verify(dataHadSignature); //得到驗證結果
System.out.println("本地的驗簽結果:"+verify);
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
}
CT_MESSAGE:即,告訴簽名方,我發過去的內容,就是簽名的原文.一般來說,因為簽名值都是位元組陣列,所以簽名方會根據約定的編碼方式,對這個簽名原文按規定的編碼方式,取它的位元組陣列之後,進行簽名
我們本地驗籤的時候,也要對原文做同樣的操作
/**
* 測試原子服務P1方式的簽名和驗籤通過
* @throws Exception
*/
@Test
public void testSign_P1_CT_MESSAGE() {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate usercert = cf.generateCertificate(new FileInputStream(USER_CERT));
PublicKey publicKey = usercert.getPublicKey();
//讀取pfx證書上的祕鑰對資訊
BouncyCastleProvider provider = new BouncyCastleProvider();
Signature signature = Signature.getInstance("SHA1withRSA");
MessageDigest digest = MessageDigest.getInstance("SHA-1", provider);
List<UserInfo> userInfoList = userInfoService.findAll();
UserInfo userInfo = userInfoList.get(0);
Map<String, Object> puserCert = new HashMap<>();
puserCert.put("userInfo", userInfo);
List<UserCert> userCertList = userCertService.getListByParam(puserCert);
UserCert userCert = userCertList.get(0);
YuanZi_P1SignRequest yuanZiP1SignRequest = new YuanZi_P1SignRequest();
yuanZiP1SignRequest.setAlias(userInfo.getAlias());
yuanZiP1SignRequest.setHashAlg("SHA1");
yuanZiP1SignRequest.setContentType("CT_MESSAGE");
String content = "我是簽名原文";
yuanZiP1SignRequest.setContent(content);
YuanZi_P1SignResponse yuanZiP1SignResponse = yuanZiService.signP1(yuanZiP1SignRequest);
System.out.println("原子服務返回的簽名值:"+yuanZiP1SignResponse.getSignedData());
byte[] dataHadSignature = Base64.decode(yuanZiP1SignResponse.getSignedData());
YuanZi_VerifyP1SignRequest yuanZiVerifyP1SignRequest = new YuanZi_VerifyP1SignRequest();
yuanZiVerifyP1SignRequest.setSignedData(yuanZiP1SignResponse.getSignedData());
yuanZiVerifyP1SignRequest.setContentType("CT_MESSAGE");
yuanZiVerifyP1SignRequest.setContent(content);
yuanZiVerifyP1SignRequest.setHashAlg("SHA1");
yuanZiVerifyP1SignRequest.setCert(userCert.getCertBuf().getCertsignBuf());
YuanZi_VerifyP1SignResponse yuanZiVerifyP1SignResponse = yuanZiService.verifyP1Sign(yuanZiVerifyP1SignRequest);
System.out.println(yuanZiVerifyP1SignResponse.getCode());
//本地驗籤
boolean verify=false;
signature.initVerify(usercert); //使用公鑰初始化簽名物件,用於驗證簽名
signature.update(content.getBytes(Charset.forName("UTF-8")));
verify= signature.verify(dataHadSignature); //得到驗證結果
System.out.println("本地的驗簽結果:"+verify);
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
}
CT_BASE64_DATA:因為有時候,我們要簽名的原文,它就是一個byte[],但是為了方便傳輸,一般簽名方都要求接受的是一個字串.所以就有了這種簽名方式.即:我們需要對簽名原文的byte[]做base64編碼,簽名方收到之後,再做解碼,並且把解碼後的byte[]進行簽名.
所以我們本身驗籤的時候,只要傳入byte[]就行.
第三方返回的簽名值,一般也是簽名值byte[]做base64編碼後的字串,所以我們要做base64解碼,才能獲取到簽名值byte[].
/**
* 測試原子服務P1方式的CT_BASE64_DATA簽名和驗籤通過
* @throws Exception
*
* 注意多種簽名方式,你傳給它要簽名的資料,和它最後執行簽名的時候,真正要籤的那個原文可能跟你傳的不是同一個(比如需要做String 轉為byte,base64編碼要解碼)
*/
@Test
public void testSign_P1_CT_BASE64_DATA() {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate usercert = cf.generateCertificate(new FileInputStream(USER_CERT));
PublicKey publicKey = usercert.getPublicKey();
//讀取pfx證書上的祕鑰對資訊
BouncyCastleProvider provider = new BouncyCastleProvider();
Signature signature = Signature.getInstance("SHA1withRSA");
MessageDigest digest = MessageDigest.getInstance("SHA-1", provider);
List<UserInfo> userInfoList = userInfoService.findAll();
UserInfo userInfo = userInfoList.get(0);
Map<String, Object> puserCert = new HashMap<>();
puserCert.put("userInfo", userInfo);
List<UserCert> userCertList = userCertService.getListByParam(puserCert);
UserCert userCert = userCertList.get(0);
YuanZi_P1SignRequest yuanZiP1SignRequest = new YuanZi_P1SignRequest();
yuanZiP1SignRequest.setAlias(userInfo.getAlias());
yuanZiP1SignRequest.setHashAlg("SHA1");
yuanZiP1SignRequest.setContentType("CT_BASE64_DATA");
byte[] dataToSign = new byte[10];
String base64ToSign = Base64.encodeBytes(dataToSign);
yuanZiP1SignRequest.setContent(base64ToSign);
YuanZi_P1SignResponse yuanZiP1SignResponse = yuanZiService.signP1(yuanZiP1SignRequest);
System.out.println("原子服務返回的簽名狀態:"+yuanZiP1SignResponse.getCode());
System.out.println("原子服務返回的簽名值:"+yuanZiP1SignResponse.getSignedData());
//簽名後的
byte[] dataHadSignature = Base64.decode(yuanZiP1SignResponse.getSignedData());
YuanZi_VerifyP1SignRequest yuanZiVerifyP1SignRequest = new YuanZi_VerifyP1SignRequest();
yuanZiVerifyP1SignRequest.setSignedData(yuanZiP1SignResponse.getSignedData());
yuanZiVerifyP1SignRequest.setContentType("CT_BASE64_DATA");
yuanZiVerifyP1SignRequest.setContent(base64ToSign);
yuanZiVerifyP1SignRequest.setHashAlg("SHA1");
yuanZiVerifyP1SignRequest.setCert(userCert.getCertBuf().getCertsignBuf());
YuanZi_VerifyP1SignResponse yuanZiVerifyP1SignResponse = yuanZiService.verifyP1Sign(yuanZiVerifyP1SignRequest);
System.out.println(yuanZiVerifyP1SignResponse.getCode());
System.out.println(yuanZiVerifyP1SignResponse.getMessage());
//本地驗籤
boolean verify=false;
signature.initVerify(publicKey); //使用公鑰初始化簽名物件,用於驗證簽名
//原子
signature.update(dataToSign);
verify= signature.verify(dataHadSignature); //得到驗證結果
System.out.println("本地的驗簽結果:"+verify);
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
}
P7簽名:
P7簽名其實也就是P1簽名的基礎上,附加一些其他的資訊(因為P1是裸簽名,只有加密串,除了本人自己,根本不知道簽名證書,公鑰,簽名演算法,簽名用的hash演算法,時間戳,簽名原文等等資訊是什麼,不便於驗證簽名,所以就需要有P7簽名作為補充,其實也就是在P1之後,在加密串的基礎上,附加這些資訊上去)
所以我們在itextpdf的原始碼上能看到,其實它做簽名的時候,也就是先構建出一個要簽名的字串,然後丟給某個私鑰進行簽名,獲得簽名值,它再對這個簽名值附加:證書鏈,時間戳等等資訊,然後構建出一個P7簽名,然後放入PDF檔案中.
PKCS#1:定義RSA公開金鑰演算法加密和簽名機制,主要用於組織PKCS#7中所描述的數字簽名和數字信封[22]。
PKCS#7:定義一種通用的訊息語法,包括數字簽名和加密等用於增強的加密機制,PKCS#7與PEM相容,所以不需其他密碼操作,就可以將加密的訊息轉換成PEM訊息[26]。