數字簽名和驗簽過程實現分析
數字簽名在網路安全領域用的比較多,可實現使用者身份的真實可靠性;實現資訊的完整性,確保資料在儲存、傳輸和處理的過程中免遭任何非授權的或非預期的修改、插入、刪除、重發等破壞,從而實現資料的真實性、有效性和一致性;實現抗抵賴性,通過數字簽名確保資訊的傳送方不能抵賴曾經發送的資訊,不能否認自己的操作行為。
下面結合著名的VPN開源專案openswan-2.4.7來理解數字簽名和解簽名的具體過程。
一、數字簽名和解籤(驗籤)過程:使用hash函式對報文資料生成摘要,使用自己的私鑰對摘要進行簽名(其實就是加密過程),生成簽名資料,把原始資料報文和簽名資料傳送給對方。接收方收到原始資料報文和簽名資料後,首先使用相同的hash函式對原始資料生成一個摘要,其次,用對方的公鑰對簽名資料進行解簽名(解密過程),這個解簽名的結果也是個摘要,最後比較這兩個摘要是否相同,如果相同那麼接收方就能確認該數字簽名是傳送方的。
用以下hash1、hash2和hash3表明不同時期的摘要:
傳送方: 簽名值=f(hash1(msg),私鑰); (msg表示原始報文資料)
接收方:hash2(msg) = f^(-1)(簽名值,公鑰);(我覺得就是f的反函式,所以寫了f^(-1)表示)
hash3(msg);
正常情況下hash2通過公鑰對“傳送方發過來的簽名值”解籤後應該等於hash1。hash1和hash3都是對原始報文資料進行摘要,所以只要最後比較hash2和hash3是否相同,就能確定訊息的完整性和證明資料是由對方傳送的(抗抵賴)。
二、openswan協商總體流程,只關注第5、6個包即可。
Initiator Responder
----------- -----------
HDR, SA -->
第1個包 main_outI1(傳送初始化資料1)
<-- HDR, SA
第2個包 main_inI1_outR1(接收到初始化資料1, 傳送響應資料1)
HDR, KE, Ni -->
第3個包 main_inR1_outI2(接收到響應資料1,傳送初始化資料2)
<-- HDR, KE, Nr
第4個包 main_inI2_outR2(接收到初始化資料2, 傳送響應資料2)
HDR*, IDii, [ CERT, ] SIG_I -->
第5個包 main_inR2_outI3(接收到響應資料2,傳送初始化資料3)
<-- HDR*, IDir, [ CERT, ] SIG_R
第6個包 main_inI3_outR3(接收到初始化資料3, 傳送響應資料3)
main_inR3(接收到響應資料3, 完成主模式協商, 建立ISAKMP SA)
由rfc2409 5.1 使用簽名方法進行IKE第一階段認證,可知簽名載荷SIG_I在第5個包中傳送給對方,對方在第6個包中對簽名進行驗籤,併發送另外一個簽名給發起方,最後發起方對收到的簽名進行驗籤。
5.1 使用簽名方法進行IKE第一階段認證
使用簽名方法時,在第二個傳輸往返中交換的輔助資訊是當前時間(nonce);
交換的認證是通過對一個相互可得到的hash值進行簽名。使用簽名認證的主模
式描述如下:
發起者 響應者
----------- -----------
HDR, SA -->
<-- HDR, SA
HDR, KE, Ni -->
<-- HDR, KE, Nr
HDR*, IDii, [ CERT, ] SIG_I -->
<-- HDR*, IDir, [ CERT, ] SIG_R
三、簽名過程
1,函式呼叫過程:
main_inR2_outI3()
->main_mode_hash()
->RSA_sign_hash()
->sign_hash()
2,具體分析(只是摘取關鍵程式碼片段):
main_inR2_outI3()
{
/* HASH_I or SIG_I out */
{
u_char hash_val[MAX_DIGEST_LEN];
size_t hash_len = main_mode_hash(st, hash_val, TRUE, &id_pbs);//對資料st和id_pbs做摘要(即原始資料msg),摘要值放在hash_val中(即hash1),摘要長度為返回值hash_len。
if (auth_payload == ISAKMP_NEXT_HASH)
{
/* HASH_I out */
if (!out_generic_raw(ISAKMP_NEXT_NONE
, &isakmp_hash_desc
, &md->rbody
, hash_val, hash_len, "HASH_I"))
return STF_INTERNAL_ERROR;
}
else
{
/* SIG_I out */
u_char sig_val[RSA_MAX_OCTETS];
size_t sig_len = RSA_sign_hash(st->st_connection
, sig_val, hash_val, hash_len);//對摘要值hash_val(hash1)做簽名,簽名使用的證書私鑰放在st->st_connection中,最終的簽名值為sig_val,長度為返回值sig_len.
if (sig_len == 0)
{
loglog(RC_LOG_SERIOUS, "unable to locate my private key for RSA Signature");
return STF_FAIL + AUTHENTICATION_FAILED;
}
if (!out_generic_raw(ISAKMP_NEXT_NONE
, &isakmp_signature_desc
, &md->rbody
, sig_val
, "SIG_I"))
return STF_INTERNAL_ERROR;
}
}
}
//hash函式
static size_t /* length of hash */
main_mode_hash(struct state *st
, u_char *hash_val /* resulting bytes */
, bool hashi /* Initiator? */
, const pb_stream *idpl) /* ID payload, as PBS; cur must be at end */
{
struct hmac_ctx ctx;
hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid);
main_mode_hash_body(st, hashi, idpl, &ctx.hash_ctx, ctx.h->hash_update);
hmac_final(hash_val, &ctx);
return ctx.hmac_digest_len;
}
//簽名函式
static size_t
RSA_sign_hash(struct connection *c
, u_char sig_val[RSA_MAX_OCTETS]
, const u_char *hash_val, size_t hash_len)
{
size_t sz = 0;
smartcard_t *sc = c->spd.this.sc;
if (sc == NULL) /* no smartcard */
{
const struct RSA_private_key *k = get_RSA_private_key(c);
if (k == NULL)
return 0; /* failure: no key to use */
sz = k->pub.k;
passert(RSA_MIN_OCTETS <= sz && 4 + hash_len < sz && sz <= RSA_MAX_OCTETS);
sign_hash(k, hash_val, hash_len, sig_val, sz);
}
……
……
}
四、驗簽過程
1,函式呼叫關係:
main_inI3_outR3()
->main_inI3_outR3_tail()
->main_id_and_auth()
->oakley_id_and_auth()
->decode_peer_id()//使用ca校驗對方證書的合法性,先不關注。
->decode_cert()
->verify_x509cert()
->RSA_check_signature()//驗籤
->take_a_crack()
->try_RSA_signature()//最終的比較過程
2,具體分析(摘取關鍵程式碼片段):
static stf_status
oakley_id_and_auth(struct msg_digest *md
, bool initiator /* are we the Initiator? */
, bool aggrmode /* aggressive mode? */
, cont_fn_t cont_fn /* continuation function */
, const struct key_continuation *kc /* current state, can be NULL */
)
{
struct state *st = md->st;
u_char hash_val[MAX_DIGEST_LEN];
size_t hash_len;
stf_status r = STF_OK;
/* ID Payload in.
* Note: this may switch the connection being used!
*/
if (!decode_peer_id(md, initiator, aggrmode))//使用ca校驗對方證書的合法性,先不關注。
return STF_FAIL + INVALID_ID_INFORMATION;
/* Hash the ID Payload.
* main_mode_hash requires idpl->cur to be at end of payload
* so we temporarily set if so.
*/
{
pb_stream *idpl = &md->chain[ISAKMP_NEXT_ID]->pbs;
u_int8_t *old_cur = idpl->cur;
idpl->cur = idpl->roof;
hash_len = main_mode_hash(st, hash_val, !initiator, idpl);//對資料st和id_pbs做摘要(即傳送方的原始資料msg),摘要值放在hash_val中(即hash3),摘要長度為返回值hash_len。
idpl->cur = old_cur;
}
switch (st->st_oakley.auth)
{
case OAKLEY_PRESHARED_KEY:
{
pb_stream *const hash_pbs = &md->chain[ISAKMP_NEXT_HASH]->pbs;
if (pbs_left(hash_pbs) != hash_len
|| memcmp(hash_pbs->cur, hash_val, hash_len) != 0)
{
DBG_cond_dump(DBG_CRYPT, "received HASH:"
, hash_pbs->cur, pbs_left(hash_pbs));
loglog(RC_LOG_SERIOUS, "received Hash Payload does not match computed value");
/* XXX Could send notification back */
r = STF_FAIL + INVALID_HASH_INFORMATION;
}
}
break;
case OAKLEY_RSA_SIG:
r = RSA_check_signature(st, hash_val, hash_len//hash_val是剛上面計算出來的hash3,這裡的st沒什麼意義,公鑰會在函式裡取到。
, &md->chain[ISAKMP_NEXT_SIG]->pbs//pbs中存放傳送方發過來的簽名值
);
……
……
}
RSA_check_signature(struct state *st
, const u_char hash_val[MAX_DIGEST_LEN]
, size_t hash_len
, const pb_stream *sig_pbs
#ifdef USE_KEYRR
, const struct pubkey_list *keys_from_dns
#endif /* USE_KEYRR */
, const struct gw_info *gateways_from_dns
)
{
const struct connection *c = st->st_connection;
struct tac_state s;
err_t dns_ugh = NULL;
s.st = st;
s.hash_val = hash_val;//hash3賦給s
s.hash_len = hash_len;
s.sig_pbs = sig_pbs;//簽名值賦給s
s.best_ugh = NULL;
s.tried_cnt = 0;
s.tn = s.tried;
/* try all gateway records hung off c */
if ((c->policy & POLICY_OPPO))
{
struct gw_info *gw;
for (gw = c->gw_info; gw != NULL; gw = gw->next)
{
/* only consider entries that have a key and are for our peer */
if (gw->gw_key_present
&& same_id(&gw->gw_id, &c->spd.that.id)
&& take_a_crack(&s, gw->key, "key saved from DNS TXT"))
return STF_OK;
}
}
/* try all appropriate Public keys */
{
struct pubkey_list *p, **pp;
int pathlen;
pp = &pubkeys;
pathlen = pathlen; /* make sure it used even with !X509 */
{
char buf[IDTOA_BUF];
DBG(DBG_CONTROL,
dntoa_or_null(buf, IDTOA_BUF, c->spd.that.ca, "%any");
DBG_log("required CA is '%s'", buf));
}
for (p = pubkeys; p != NULL; p = *pp)//獲取需要使用的公鑰,如何獲取不再深入。
{
struct pubkey *key = p->key;
if (key->alg == PUBKEY_ALG_RSA && same_id(&c->spd.that.id, &key->id)
&& trusted_ca(key->issuer, c->spd.that.ca, &pathlen))//最終匹配到的對方公鑰由指標key指向。
{
time_t now;
{
char buf[IDTOA_BUF];
DBG(DBG_CONTROL,
dntoa_or_null(buf, IDTOA_BUF, key->issuer, "%any");
DBG_log("key issuer CA is '%s'", buf));
}
/* check if found public key has expired */
time(&now);
if (key->until_time != UNDEFINED_TIME && key->until_time < now)
{
loglog(RC_LOG_SERIOUS,
"cached RSA public key has expired and has been deleted");
*pp = free_public_keyentry(p);
continue; /* continue with next public key */
}
if (take_a_crack(&s, key, "preloaded key"))//傳入s(包含簽名值和hash3)和公鑰。
return STF_OK;
}
pp = &p->next;
}
}
……
……
}
take_a_crack(struct tac_state *s
, struct pubkey *kr
, const char *story USED_BY_DEBUG)
{
err_t ugh = try_RSA_signature(s->hash_val, s->hash_len, s->sig_pbs
, kr, s->st);//s->hash_val為hash3, s->sig_pbs為簽名值,kr為公鑰。
const struct RSA_public_key *k = &kr->u.rsa;
……
……
}
static err_t
try_RSA_signature(const u_char hash_val[MAX_DIGEST_LEN], size_t hash_len
, const pb_stream *sig_pbs, struct pubkey *kr
, struct state *st)
{
……
……
/* We have the decoded hash: see if it matches. */
if (memcmp(hash_val, hash_in_s, hash_len) != 0)//hash_val還是傳進來的hash3,沒有變化;hash_in_s是由公鑰對簽名值進行了解籤的結果(即hash2),如中解簽名不再分析。所以這裡比較hash3 == hash2 ? 如果相同,那麼驗籤通過,否則失敗。
{
DBG_cond_dump(DBG_CRYPT, "decrypted SIG", s, sig_len);
DBG_cond_dump(DBG_CRYPT, "computed HASH", hash_val, hash_len);
/* XXX notification: INVALID_HASH_INFORMATION */
return "9" "authentication failure: received SIG does not match computed HASH, but message is we
}
unreference_key(&st->st_peer_pubkey);
st->st_peer_pubkey = reference_key(kr);
return NULL; /* happy happy */
}