目錄
- 簡介
- CMAC的工作原理
- CMAC示例
簡介
- CMAC(Cipher-based MAC),基於密碼的MAC,是一種基於密碼的MAC演算法,它基於塊密碼演算法(如AES)和一個金鑰來生成認證碼。
- CMAC是一種對稱金鑰加密演算法,通常與對稱金鑰演算法(如AES)結合使用,以提供訊息的完整性和真實性驗證
- 本文主要用於安全演算法驗證(基於AES),故有些名詞可能不太準確,具體演算法可參考 https://datatracker.ietf.org/doc/html/rfc4493 .
(以上來自維基百科: CMAC1或 CMAC2)
CMAC的工作原理
-
初始化:CMAC使用一個固定長度的金鑰來初始化。金鑰的長度通常與底層的對稱加密演算法(如AES)相關聯。
-
分塊處理:首先,將訊息分成多個固定長度的塊。如果訊息長度不是塊大小的倍數,則可以使用填充來將其填充到合適的大小。
-
生成子金鑰:**根據初始金鑰生成用於加密的子金鑰。通常,CMAC使用兩個不同的子金鑰,分別用於生成左右兩個分支的子金鑰。
-
生成MAC:
- 左分支:將訊息的每個塊與左分支的子金鑰進行加密。對於最後一個塊,如果長度不夠,則使用填充。
- 右分支:將左分支的結果進行異或運算,然後再與右分支的子金鑰進行加密。
- 結果:將右分支的加密結果擷取指定長度作為最終的認證碼。
- 認證:將訊息的認證碼與生成的MAC進行比較。如果兩者相匹配,則訊息未被篡改,認證成功。
代號(Char) | 含義(Meaning) |
---|---|
b | 加密塊的位長(bit) |
K | 用於AES的金鑰 |
K1 | 子金鑰1,用於左分支 |
K2 | 子金鑰2,用於右分支 |
M | 訊息 |
M_i | 訊息塊i |
CMAC示例
- 基於python的驗證程式碼如下:
- 需要安裝Crypto庫 :
- 若提示Crypto庫未找到,改下庫資料夾名稱:
- 程式碼如下
from Crypto.Cipher import AES
from Crypto.Hash import CMAC
from binascii import hexlify, unhexlify
key = unhexlify('0102030405060708090a0b0c0d0e0f10')
#print("key:", key.decode())
seed = unhexlify('100f0e0d0c0b0a090807060504030201')
#print("seed:", seed.decode())
mac = CMAC.new(key,seed,ciphermod=AES)
print("AES_CMAC:",mac.hexdigest())
# result : 5becb7b36a0c7e019e9caf10f3971b00
- 基於c/c++的驗證程式碼如下:
#include "aes.h"
#include "windows.h"
#include <stdio.h>
#include <stdint.h>
#include <string.h>
/*****************************************************************************
* @description : 這部分需要自己實現,採用AES-ECB的演算法
*
* @param ( uint8_t ) *key
*
* @param ( uint8_t ) *input
*
* @param ( uint8_t ) *output
*
* @return ( none )
*****************************************************************************/
void AES_128(const uint8_t *key, const uint8_t *input, uint8_t *output)
{
// 實現 AES-128 加密,或者使用其他庫
int a = 16;
aes_encrypt_ecb(key, 16, input, 16, output, &a);
}
/* For CMAC Calculation */
const unsigned char const_Rb[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87};
const unsigned char const_Zero[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/*****************************************************************************
* @description : 計算XOR的結果
*
* @param ( unsigned char ) *a 需要計算的資料
*
* @param ( unsigned char ) *b 異或的物件
*
* @param ( unsigned char ) *out 輸出結果
*
* @return ( none )
*****************************************************************************/
void xor_128(unsigned char *a, const unsigned char *b, unsigned char *out)
{
int i;
for (i = 0; i < 16; i++)
{
out[i] = a[i] ^ b[i];
}
}
void print_hex(char *str, unsigned char *buf, int len)
{
int i;
for (i = 0; i < len; i++)
{
if ((i % 16) == 0 && i != 0)
printf(str);
printf("%02x", buf[i]);
if ((i % 4) == 3)
printf(" ");
if ((i % 16) == 15)
printf("\n");
}
if ((i % 16) != 0)
printf("\n");
}
void print128(unsigned char *bytes)
{
int j;
for (j = 0; j < 16; j++)
{
printf("%02x", bytes[j]);
if ((j % 4) == 3)
printf(" ");
}
}
void leftshift_onebit(unsigned char *input, unsigned char *output)
{
int i;
unsigned char overflow = 0;
for (i = 15; i >= 0; i--)
{
output[i] = input[i] << 1;
output[i] |= overflow;
overflow = (input[i] & 0x80) ? 1 : 0;
}
return;
}
void MAC_GenSubKey(unsigned char *key, unsigned char *K1, unsigned char *K2)
{
unsigned char L[16] = {
0,
};
unsigned char Z[16] = {
0,
};
unsigned char tmp[16] = {
0,
};
int i;
for (i = 0; i < 16; i++)
Z[i] = 0;
AES_128(key, Z, L);
if ((L[0] & 0x80) == 0)
{ /* If MSB(L) = 0, then K1 = L << 1 */
leftshift_onebit(L, K1);
}
else
{ /* Else K1 = ( L << 1 ) (+) Rb */
leftshift_onebit(L, tmp);
xor_128(tmp, const_Rb, K1);
}
if ((K1[0] & 0x80) == 0)
{
leftshift_onebit(K1, K2);
}
else
{
leftshift_onebit(K1, tmp);
xor_128(tmp, const_Rb, K2);
}
printf("\nLeft:\n");
for (int j = 0; j < 15; j++)
{
printf("%02x", K1[j]);
}
printf("\nRight:\n");
for (int j = 0; j < 15; j++)
{
printf("%02x", K1[j]);
}
printf("\n");
}
void padding(unsigned char *lastb, unsigned char *pad, int length)
{
int j;
/* original last block */
for (j = 0; j < 16; j++)
{
if (j < length)
{
pad[j] = lastb[j];
}
else if (j == length)
{
pad[j] = 0x80;
}
else
{
pad[j] = 0x00;
}
}
}
void AES_CMAC(unsigned char *key, unsigned char *input, int length,
unsigned char *mac)
{
unsigned char X[16], Y[16], M_last[16], padded[16];
unsigned char K1[16], K2[16];
int n, i, flag;
MAC_GenSubKey(key, K1, K2);
n = (length + 15) / 16; /* n is number of rounds */
if (n == 0)
{
n = 1;
flag = 0;
}
else
{
if ((length % 16) == 0)
{ /* last block is a complete block */
flag = 1;
}
else
{ /* last block is not complete block */
flag = 0;
}
}
if (flag)
{ /* last block is complete block */
xor_128(&input[16 * (n - 1)], K1, M_last);
}
else
{
padding(&input[16 * (n - 1)], padded, length % 16);
xor_128(padded, K2, M_last);
}
for (i = 0; i < 16; i++)
X[i] = 0;
for (i = 0; i < n - 1; i++)
{
xor_128(X, &input[16 * i], Y); /* Y := Mi (+) X */
AES_128(key, Y, X); /* X := AES-128(KEY, Y); */
}
xor_128(X, M_last, Y);
AES_128(key, Y, X);
for (i = 0; i < 16; i++)
{
mac[i] = X[i];
}
}
/**
key : 100f0e0d0c0b0a090807060504030201
seed:0102030405060708090a0b0c0d0e0f10
result: 95c6652305da28e31d6a7ab99dfd2998
**/
int main()
{
unsigned char L[16], K1[16], K2[16], T[16], TT[12];
unsigned char M[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
unsigned char key[16] = {
0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
printf("--------------------------------------------------\n");
printf("key:\n");
for (int i = 0; i < 15; i++)
{
printf("%02x", key[i]);
}
printf("\n");
MAC_GenSubKey(key, K1, K2);
printf("M ");
print_hex(" ", M, 16);
AES_CMAC(key, M, 16, T);
printf("AES_CMAC ");
print128(T);
printf("\n");
printf("--------------------------------------------------\n");
system("pause");
return 0;
}
- 示例資料
以下資料可供參考,用於驗證演算法準確性:
訊息(Seed/Message) | 金鑰(Key) | 結果(Result/Token) |
---|---|---|
100f0e0d0c0b0a090807060504030201 | 0102030405060708090a0b0c0d0e0f10 | 5becb7b36a0c7e019e9caf10f3971b00 |
0102030405060708090a0b0c0d0e0f10 | 100f0e0d0c0b0a090807060504030201 | 95c6652305da28e31d6a7ab99dfd2998 |
66B0CF31F56AC16ABF4610DF87A1AE20 | 3D2E6DE2A12517BAC5B31BBD0E7E3B54 | 0d4de052272cc4f56e2a4fbc8dcfa931 |