C++ 檔案 HASH 計算 範例(SHA1​、SHA512)​

C++ 檔案 HASH 計算 範例(SHA1​、SHA512)​

C++ 檔案 HASH 計算 範例(SHA1SHA512)



資料來源

CPP_FILE_HASH – https://github.com/Anubisss/md5-hasher
CPP_SHA1 – https://blog.csdn.net/qq_16542775/article/details/52301995
CPP_SHA512 – https://blog.csdn.net/luo_xianming/article/details/24500243


線上驗證網址: https://md5file.com/calculator [SHA1和 SHA512 結果一致]


GITHUB: https://github.com/jash-git/CPP_Calculator_FILE_HASH


CPP_SHA1

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

using namespace std;

/*
資料來源:https://blog.csdn.net/qq_16542775/article/details/52301995
驗證:https://md5file.com/calculator
*/

#undef BIG_ENDIAN_HOST
typedef unsigned int u32;

/****************
* Rotate a 32 bit integer by n bytes
*/
#if defined(__GNUC__) && defined(__i386__)
static inline u32
    rol( u32 x, int n)
{
    __asm__("roll %%cl,%0"
        :"=r" (x)
        :"0" (x),"c" (n));
    return x;
}
#else
#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) )
#endif


typedef struct {
    u32  h0,h1,h2,h3,h4;
    u32  nblocks;
    unsigned char buf[64];
    int  count;
} SHA1_CONTEXT;



void
    sha1_init( SHA1_CONTEXT *hd )
{
    hd->h0 = 0x67452301;
    hd->h1 = 0xefcdab89;
    hd->h2 = 0x98badcfe;
    hd->h3 = 0x10325476;
    hd->h4 = 0xc3d2e1f0;
    hd->nblocks = 0;
    hd->count = 0;
}


/****************
* Transform the message X which consists of 16 32-bit-words
*/
static void
    transform( SHA1_CONTEXT *hd, unsigned char *data )
{
    u32 a,b,c,d,e,tm;
    u32 x[16];

    /* get values from the chaining vars */
    a = hd->h0;
    b = hd->h1;
    c = hd->h2;
    d = hd->h3;
    e = hd->h4;

#ifdef BIG_ENDIAN_HOST
    memcpy( x, data, 64 );
#else
    {
        int i;
        unsigned char *p2;
        for(i=0, p2=(unsigned char*)x; i < 16; i++, p2 += 4 )
        {
            p2[3] = *data++;
            p2[2] = *data++;
            p2[1] = *data++;
            p2[0] = *data++;
        }
    }
#endif


#define K1  0x5A827999L
#define K2  0x6ED9EBA1L
#define K3  0x8F1BBCDCL
#define K4  0xCA62C1D6L
#define F1(x,y,z)   ( z ^ ( x & ( y ^ z ) ) )
#define F2(x,y,z)   ( x ^ y ^ z )
#define F3(x,y,z)   ( ( x & y ) | ( z & ( x | y ) ) )
#define F4(x,y,z)   ( x ^ y ^ z )


#define M(i) ( tm =   x[i&0x0f] ^ x[(i-14)&0x0f] \
    ^ x[(i-8)&0x0f] ^ x[(i-3)&0x0f] \
    , (x[i&0x0f] = rol(tm,1)) )

#define R(a,b,c,d,e,f,k,m)  do { e += rol( a, 5 )     \
    + f( b, c, d )  \
    + k       \
    + m;          \
    b = rol( b, 30 );    \
    } while(0)
    R( a, b, c, d, e, F1, K1, x[ 0] );
    R( e, a, b, c, d, F1, K1, x[ 1] );
    R( d, e, a, b, c, F1, K1, x[ 2] );
    R( c, d, e, a, b, F1, K1, x[ 3] );
    R( b, c, d, e, a, F1, K1, x[ 4] );
    R( a, b, c, d, e, F1, K1, x[ 5] );
    R( e, a, b, c, d, F1, K1, x[ 6] );
    R( d, e, a, b, c, F1, K1, x[ 7] );
    R( c, d, e, a, b, F1, K1, x[ 8] );
    R( b, c, d, e, a, F1, K1, x[ 9] );
    R( a, b, c, d, e, F1, K1, x[10] );
    R( e, a, b, c, d, F1, K1, x[11] );
    R( d, e, a, b, c, F1, K1, x[12] );
    R( c, d, e, a, b, F1, K1, x[13] );
    R( b, c, d, e, a, F1, K1, x[14] );
    R( a, b, c, d, e, F1, K1, x[15] );
    R( e, a, b, c, d, F1, K1, M(16) );
    R( d, e, a, b, c, F1, K1, M(17) );
    R( c, d, e, a, b, F1, K1, M(18) );
    R( b, c, d, e, a, F1, K1, M(19) );
    R( a, b, c, d, e, F2, K2, M(20) );
    R( e, a, b, c, d, F2, K2, M(21) );
    R( d, e, a, b, c, F2, K2, M(22) );
    R( c, d, e, a, b, F2, K2, M(23) );
    R( b, c, d, e, a, F2, K2, M(24) );
    R( a, b, c, d, e, F2, K2, M(25) );
    R( e, a, b, c, d, F2, K2, M(26) );
    R( d, e, a, b, c, F2, K2, M(27) );
    R( c, d, e, a, b, F2, K2, M(28) );
    R( b, c, d, e, a, F2, K2, M(29) );
    R( a, b, c, d, e, F2, K2, M(30) );
    R( e, a, b, c, d, F2, K2, M(31) );
    R( d, e, a, b, c, F2, K2, M(32) );
    R( c, d, e, a, b, F2, K2, M(33) );
    R( b, c, d, e, a, F2, K2, M(34) );
    R( a, b, c, d, e, F2, K2, M(35) );
    R( e, a, b, c, d, F2, K2, M(36) );
    R( d, e, a, b, c, F2, K2, M(37) );
    R( c, d, e, a, b, F2, K2, M(38) );
    R( b, c, d, e, a, F2, K2, M(39) );
    R( a, b, c, d, e, F3, K3, M(40) );
    R( e, a, b, c, d, F3, K3, M(41) );
    R( d, e, a, b, c, F3, K3, M(42) );
    R( c, d, e, a, b, F3, K3, M(43) );
    R( b, c, d, e, a, F3, K3, M(44) );
    R( a, b, c, d, e, F3, K3, M(45) );
    R( e, a, b, c, d, F3, K3, M(46) );
    R( d, e, a, b, c, F3, K3, M(47) );
    R( c, d, e, a, b, F3, K3, M(48) );
    R( b, c, d, e, a, F3, K3, M(49) );
    R( a, b, c, d, e, F3, K3, M(50) );
    R( e, a, b, c, d, F3, K3, M(51) );
    R( d, e, a, b, c, F3, K3, M(52) );
    R( c, d, e, a, b, F3, K3, M(53) );
    R( b, c, d, e, a, F3, K3, M(54) );
    R( a, b, c, d, e, F3, K3, M(55) );
    R( e, a, b, c, d, F3, K3, M(56) );
    R( d, e, a, b, c, F3, K3, M(57) );
    R( c, d, e, a, b, F3, K3, M(58) );
    R( b, c, d, e, a, F3, K3, M(59) );
    R( a, b, c, d, e, F4, K4, M(60) );
    R( e, a, b, c, d, F4, K4, M(61) );
    R( d, e, a, b, c, F4, K4, M(62) );
    R( c, d, e, a, b, F4, K4, M(63) );
    R( b, c, d, e, a, F4, K4, M(64) );
    R( a, b, c, d, e, F4, K4, M(65) );
    R( e, a, b, c, d, F4, K4, M(66) );
    R( d, e, a, b, c, F4, K4, M(67) );
    R( c, d, e, a, b, F4, K4, M(68) );
    R( b, c, d, e, a, F4, K4, M(69) );
    R( a, b, c, d, e, F4, K4, M(70) );
    R( e, a, b, c, d, F4, K4, M(71) );
    R( d, e, a, b, c, F4, K4, M(72) );
    R( c, d, e, a, b, F4, K4, M(73) );
    R( b, c, d, e, a, F4, K4, M(74) );
    R( a, b, c, d, e, F4, K4, M(75) );
    R( e, a, b, c, d, F4, K4, M(76) );
    R( d, e, a, b, c, F4, K4, M(77) );
    R( c, d, e, a, b, F4, K4, M(78) );
    R( b, c, d, e, a, F4, K4, M(79) );

    /* Update chaining vars */
    hd->h0 += a;
    hd->h1 += b;
    hd->h2 += c;
    hd->h3 += d;
    hd->h4 += e;
}


/* Update the message digest with the contents
* of INBUF with length INLEN.
*/
static void
    sha1_write( SHA1_CONTEXT *hd, unsigned char *inbuf, size_t inlen)
{
    if( hd->count == 64 ) { /* flush the buffer */
        transform( hd, hd->buf );
        hd->count = 0;
        hd->nblocks++;
    }
    if( !inbuf )
        return;
    if( hd->count ) {
        for( ; inlen && hd->count < 64; inlen-- )
            hd->buf[hd->count++] = *inbuf++;
        sha1_write( hd, NULL, 0 );
        if( !inlen )
            return;
    }

    while( inlen >= 64 ) {
        transform( hd, inbuf );
        hd->count = 0;
        hd->nblocks++;
        inlen -= 64;
        inbuf += 64;
    }
    for( ; inlen && hd->count < 64; inlen-- )
        hd->buf[hd->count++] = *inbuf++;
}


/* The routine final terminates the computation and
* returns the digest.
* The handle is prepared for a new cycle, but adding bytes to the
* handle will the destroy the returned buffer.
* Returns: 20 bytes representing the digest.
*/

static void
    sha1_final(SHA1_CONTEXT *hd)
{
    u32 t, msb, lsb;
    unsigned char *p;

    sha1_write(hd, NULL, 0); /* flush */;

    t = hd->nblocks;
    /* multiply by 64 to make a byte count */
    lsb = t << 6;
    msb = t >> 26;
    /* add the count */
    t = lsb;
    if( (lsb += hd->count) < t )
        msb++;
    /* multiply by 8 to make a bit count */
    t = lsb;
    lsb <<= 3;
    msb <<= 3;
    msb |= t >> 29;

    if( hd->count < 56 ) { /* enough room */
        hd->buf[hd->count++] = 0x80; /* pad */
        while( hd->count < 56 )
            hd->buf[hd->count++] = 0;  /* pad */
    }
    else { /* need one extra block */
        hd->buf[hd->count++] = 0x80; /* pad character */
        while( hd->count < 64 )
            hd->buf[hd->count++] = 0;
        sha1_write(hd, NULL, 0);  /* flush */;
        memset(hd->buf, 0, 56 ); /* fill next block with zeroes */
    }
    /* append the 64 bit count */
    hd->buf[56] = msb >> 24;
    hd->buf[57] = msb >> 16;
    hd->buf[58] = msb >>  8;
    hd->buf[59] = msb       ;
    hd->buf[60] = lsb >> 24;
    hd->buf[61] = lsb >> 16;
    hd->buf[62] = lsb >>  8;
    hd->buf[63] = lsb       ;
    transform( hd, hd->buf );

    p = hd->buf;
#ifdef BIG_ENDIAN_HOST
#define X(a) do { *(u32*)p = hd->h##a ; p += 4; } while(0)
#else /* little endian */
#define X(a) do { *p++ = hd->h##a >> 24; *p++ = hd->h##a >> 16;    \
    *p++ = hd->h##a >> 8; *p++ = hd->h##a; } while(0)
#endif
    X(0);
    X(1);
    X(2);
    X(3);
    X(4);
#undef X
}
/*输出文件的SHA1值
* FileNameInPut:文件路径
*/
void GetFileSHA1(char *FileNameInPut)
{
    if(FileNameInPut==NULL)
    {
        printf("\nUsage:\n     <EXEFILE> <FILENAME>\n ");
        return;
    }
    FILE *fp;
    char buffer[4096];
    size_t n;
    SHA1_CONTEXT ctx;
    int i;

    fp=fopen (FileNameInPut, "rb");
    if (!fp)
    {
        printf("Open FILE '%s' ERROR\n", FileNameInPut);
        return;
    }
    sha1_init (&ctx);
    while ( (n = fread (buffer, 1, sizeof buffer, fp)))     sha1_write (&ctx, (unsigned char *)buffer, n);
    if (ferror (fp))
    {
        printf("Read FILE '%s' ERROR\n", FileNameInPut);
        return;
    }
    sha1_final (&ctx);
    fclose (fp);

    for ( i=0; i < 20; i++)
    {
        printf("%02x",ctx.buf[i]);
    }
}
/*获取文件的SHA1值,如果发生错误则将错误信息写入outError
* FileNameInPut:文件路径
* outSHA1:SHA1输出变量
* outError:错误信息输出变量
* returns:outSHA1
*/
char *GetFileSHA1(char *FileNameInPut, char *outSHA1, char *outError)
{
    if(FileNameInPut==NULL)
    {
        if (outError != NULL)
        {
            sprintf(outError, "%s", "FileNameInPut Is NULL");
        }
        return outSHA1;
    }
    FILE *fp;
    char buffer[4096];
    size_t n;
    SHA1_CONTEXT ctx;
    int i;

    fp=fopen( FileNameInPut, "rb");
    if (!fp)
    {
        if (outError != NULL)
        {
            sprintf(outError, "Open FILE '%s' ERROR\n", FileNameInPut);
        }
        return outSHA1;
    }
    sha1_init (&ctx);
    while ( (n = fread (buffer, 1, sizeof buffer, fp)))     sha1_write (&ctx, (unsigned char *)buffer, n);
    if (ferror (fp))
    {
        if (outError != NULL)
        {
            sprintf(outError, "Read FILE '%s' ERROR\n", FileNameInPut);
        }
        return outSHA1;
    }
    sha1_final (&ctx);
    fclose (fp);

    for ( i=0; i < 20; i++)
    {
        sprintf(outSHA1 + 2*i, "%02x", (unsigned char)ctx.buf[i]);
    }
    outSHA1[2*i] = '\0';
    return outSHA1;
}

void pause()
{
    printf("Press Enter key to continue...");
    fgetc(stdin);
}

int main(int argc, char **argv)
{
    GetFileSHA1(*(argv+1));

    printf("\r\n");
    char sha1[41] = { 0 };
    char eror[256] = { 0 };
    printf("%s\r\n", GetFileSHA1(*(argv+1), sha1, NULL));
    if (strlen(eror) != 0)
    {
        printf("SHA1 ERROR : %s\n", eror);
    }

    printf("%s\r\n", GetFileSHA1(*(argv+1), sha1, eror));
    if (strlen(eror) != 0)
    {
        printf("SHA1 ERROR : %s\n", eror);
    }

    pause();
    return 0;
}

CPP_SHA512

#include <iostream>
#include <fstream>
#include <cstdio>

using namespace std;

/*
資料來源:https://blog.csdn.net/luo_xianming/article/details/24500243
驗證:https://md5file.com/calculator
*/

unsigned long long data[0x2000000];//载入数据、
unsigned long long T1,T2,W[80];//本轮使用的消息,和分组中保存的消息
unsigned long long HashI_1[8];//中间结果
unsigned long long HashI[8];//最终结果
const unsigned long long Kt[80] = {//80个常数
		0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL,
        0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
        0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL,
        0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
        0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL,
        0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
        0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL,
        0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
        0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
        0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
        0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL,
        0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
        0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL,
        0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
        0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
        0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
        0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL,
        0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
        0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL,
        0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
        0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
        0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
        0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL,
        0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
        0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL,
        0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
        0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL,
};
void InitializeHash(){//初始化 HashI_1 HashI
	HashI[0] = 0x6a09e667f3bcc908;
	HashI[1] = 0xbb67ae8584caa73b;
	HashI[2] = 0x3c6ef372fe94f82b;
	HashI[3] = 0xa54ff53a5f1d36f1;
	HashI[4] = 0x510e527fade682d1;
	HashI[5] = 0x9b05688c2b3e6c1f;
	HashI[6] = 0x1f83d9abfb41bd6b;
	HashI[7] = 0x5be0cd19137e2179;
}
unsigned long long ROTR(unsigned long long x,int n)//循环右移n位
{
	return ((x>>n) | (x<<(64-n))) ;
}
void SHA_512(int N){
	int i,t,j;
	for(i =0; i < N;i++){
		for(j =0; j < 16;j++)
			W[j] = data [ i*16 +j];//从全部数据中载入本次所需的消息
		for(j = 16;j < 80;j++)//将16-79轮的信息计算出
			W[j] = (ROTR(W[j-2],19) ^ ROTR(W[j-2],61) ^ (W[j-2]>>6) ) + W[j-7] + ( ROTR(W[j-15],1) ^ ROTR(W[j-15],8) ^ (W[j-15]>>7)) + W[j-16];
		for(j = 0;j<8;j++)
			HashI_1[j] = HashI[j];//每次开始输入之前,将之前得到的输出读入,然后对中间的hashI_1值进行操作,输出给HashI
		for(t =0 ;t < 80 ;t++){//80轮操作
			T1 = HashI_1[7] + ((HashI_1[4] & HashI_1[5]) ^ ( (~HashI_1[4]) & HashI_1[6]))
				+ (ROTR(HashI_1[4],14) ^ ROTR(HashI_1[4],18) ^ ROTR(HashI_1[4],41)) +W[t] + Kt[t];
			T2 = (ROTR(HashI_1[0],28) ^ ROTR(HashI_1[0],34) ^ ROTR(HashI_1[0],39))
				+ ((HashI_1[0] & HashI_1[1]) ^ (HashI_1[0] & HashI_1[2]) ^ (HashI_1[1] & HashI_1[2]));


			HashI_1[7] = HashI_1[6];
			HashI_1[6] = HashI_1[5];
			HashI_1[5] = HashI_1[4];
			HashI_1[4] = HashI_1[3] + T1;
			HashI_1[3] = HashI_1[2];
			HashI_1[2] = HashI_1[1];
			HashI_1[1] = HashI_1[0];
			HashI_1[0] = T1 + T2;
		}
		for(j = 0;j < 8;j++)
			HashI[j] += HashI_1[j];//得到输出
	}
}
void pause()
{
    printf("Press Enter key to continue...");
    fgetc(stdin);
}
int main(int argc, char **argv)
{
    if(argc>1)
    {
        int i,t,k ,l,j=0;
        int N ,M;//n个1024,M个256m
        unsigned char lastChar[128];
        unsigned long long TxtLength;
        unsigned long long r;
        InitializeHash();
        fstream dataf(*(argv+1),ios::in | ios::binary );
        dataf.seekp(0,ios::end);
        TxtLength = dataf.tellp();//获得文件的大小
        dataf.seekp(0,ios::beg);
        N = 1<<21;//256m中 含有 1^21个的1024
        M = (TxtLength>>28) + 1;//获得数据有多少个256m的块
        for( t = 0; t < M;t++){
            if( t == M-1){
                N =(TxtLength - (1<<28)*(M-1)) >>7 ;//当只剩下最后一组256m时,计算剩下的1024组数-1
                for( i =0;i< N;i++){//将剩下的满1024的组先读入
                    dataf.read((char*)lastChar,128);//一次读取128个char
                    for(k = 0;k<16;k++){
                        data[j] = 0;
                        for(l = 0;l < 8;l++)
                            data[j] = (data[j]<<8) | lastChar[k*8 + l];
                        j++;
                    }
                }
                N = TxtLength - (1<<28)*(M-1) - (N<<7);//计算最后剩下的字节数
                for( i = 0;i < N;i++)
                    dataf.read((char*)(&lastChar[i]),1);
                if( i >= 112){//补余时,若最后一段大于896则必须再加一层1024.
                    lastChar[i++] = 128;//最高位填充1之后填充0
                    for(;i < 128;i++)
                        lastChar[i] = 0;
                    for(i = 0;i < 16;i++){
                        data[j] = 0;
                        for(k = 0;k < 8;k++)
                            data[j] = (data[j]<<8) | lastChar[i*8 + k];
                    j++;
                    }
                    for(i=0;i < 112;i++)//新的1024行要再次填充到896位
                        lastChar[i] = 0;
                }else{
                    lastChar[i++] = 128;//最高位填充1之后填充0
                    for(;i < 112;i++)
                        lastChar[i] = 0;
                }
                //最后128位为消息长度,第一个数固定为0,第二个数直接为TextLength * 8
                //将数据从lastChar数组中读入到data数组中
                for(i = 0;i < 14;i++){
                    data[j] = 0;
                    for(k = 0;k < 8;k++)
                        data[j] = (data[j]<<8) | lastChar[i*8 + k];
                    j++;
                }
                data[j++] = 0;
                data[j++] = TxtLength<<3 ;
                N = j>>4;//j的数量肯定是合理的,则可以由此时j的数量得到最后1024的组数
                SHA_512(N);//进行hash
            }else{
                for( i =0;i<N;i++){
                    dataf.read((char*)lastChar,128);
                    for(k = 0;k<16;k++){
                        data[j] = 0;
                    for(l = 0;l < 8;l++)
                        data[j] = (data[j]<<8) | lastChar[k*8 + l];
                    j++;
                    }
                }
                SHA_512(N);//
            }
        }
        dataf.close();

        cout<<"SHA-512 ANS:"<<endl;
        for(j = 0; j < 8;j++)
            printf("%016I64x",HashI[j]);//printf("%016I64x\n",HashI[j]);

        printf("\n");
    }
    else
    {
        cout <<"Not input file\n";
    }


	pause();
	return 0;
} 



2 thoughts on “C++ 檔案 HASH 計算 範例(SHA1​、SHA512)​

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *