SHA512系列哈希算法原理及实现(附源码)

相关文章:

最近陆续造了一批哈希算法的轮子,包括MD家族(包括MD2/MD4/MD5), SHA1, SHA2家族(SHA224, SHA256, SHA384, SHA512),SHA3家族以及国密SM3算法。

原来打算将每一个算法都详细分析并实现,现在看来,这个工作短时间可能无法完成,所以先将源码发上来。

这部分实现的源码完全参考官方文档的算法描述,连变量名也尽可能和官方文档中的变量保持一致,方便学习。

本篇主要是描述SHA512系列哈希算法的原理及实现,SHA512系列的哈希函数都是基于SHA512哈希函数扩展而来,这些列主要包括:

  • SHA512
  • SHA384 (初始化常量和SHA512不一样,结果哈希从512比特截断为384比特)
  • SHA512/224 (初始化常量和SHA512不一样,哈希结果从512比特截断为224比特,兼容SHA224)
  • SHA512/256 (初始化常量和SHA512不一样,哈希结果从512比特截断为256比特,兼容SHA256)
  • SHA512/t (t值不同,计算得到的初始化常量不同,哈希结果从512比特截断为t比特)

变长版本的SHA512t中:

  • 如果t=224,其结果和SHA512/224一样;
  • 如果t=256,其结果和SHA512/256一样;
  • FIPS 180-4中指出,没有t=384的变长版本

另外, SHA512系列函数的API封装调用接口参考了openssl官方的接口,完全兼容,无缝对接。会使用这里的接口,就会使用openssl的库函数接口,甚至连代码都不需要修改。

除了实现的源码外,还另外附带了一个测试例子,这个测试例子不仅仅是用于测试哈希算法的实现是否正确,还可以提供了"-f"/"-s"等选项用于对任意文件和字符串进行哈希,因此作为一个工具使用,类似系统内置的md5sum/sha1sum。

SHA512的实现源码

1. 头文件sha512.c
/*
 * @        file: sha512.h
 * @ description: header file for sha512.c
 * @      author: Gu Yongqiang
 * @        blog: https://blog.csdn.net/guyongqiangx
 */
#ifndef __ROCKY_SHA512__H
#define __ROCKY_SHA512__H

#define ERR_OK           0
#define ERR_ERR         -1  /* generic error */
#define ERR_INV_PARAM   -2  /* invalid parameter */
#define ERR_TOO_LONG    -3  /* too long */
#define ERR_STATE_ERR   -4  /* state error */

typedef unsigned char      uint8_t;
typedef unsigned short     uint16_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;
typedef struct {
    uint64_t high; /* high 64 bits */
    uint64_t low;  /*  low 64 bits */
} uint128_t;

typedef struct sha512_context {
    /* message total length in bytes */
    uint128_t total;

    /* intermedia hash value for each block */
    struct {
        uint64_t a;
        uint64_t b;
        uint64_t c;
        uint64_t d;
        uint64_t e;
        uint64_t f;
        uint64_t g;
        uint64_t h;
    }hash;

    /* last block */
    struct {
        uint32_t used;      /* used bytes */
        uint8_t  buf[128];  /* block data buffer */
    }last;

    uint32_t ext;           /* t value of SHA512/t */
}SHA512_CTX;

/* https://www.openssl.org/docs/man1.1.1/man3/SHA256_Final.html */
int SHA384_Init(SHA512_CTX *c);
int SHA384_Update(SHA512_CTX *c, const void *data, size_t len);
int SHA384_Final(unsigned char *md, SHA512_CTX *c);
unsigned char *SHA384(const unsigned char *d, size_t n, unsigned char *md);

int SHA512_Init(SHA512_CTX *c);
int SHA512_Update(SHA512_CTX *c, const void *data, size_t len);
int SHA512_Final(unsigned char *md, SHA512_CTX *c);
unsigned char *SHA512(const unsigned char *d, size_t n, unsigned char *md);

/* SHA512/224 */
int SHA512_224_Init(SHA512_CTX *c);
int SHA512_224_Update(SHA512_CTX *c, const void *data, size_t len);
int SHA512_224_Final(unsigned char *md, SHA512_CTX *c);
unsigned char *SHA512_224(const unsigned char *d, size_t n, unsigned char *md);

/* SHA512/256 */
int SHA512_256_Init(SHA512_CTX *c);
int SHA512_256_Update(SHA512_CTX *c, const void *data, size_t len);
int SHA512_256_Final(unsigned char *md, SHA512_CTX *c);
unsigned char *SHA512_256(const unsigned char *d, size_t n, unsigned char *md);

int SHA512t_Init(SHA512_CTX *c, unsigned int t);
int SHA512t_Update(SHA512_CTX *c, const void *data, size_t len);
int SHA512t_Final(unsigned char *md, SHA512_CTX *c);
unsigned char *SHA512t(const unsigned char *d, size_t n, unsigned char *md, unsigned int t);

#endif
2. 代码文件sha512.c
/*
 * @        file: sha512.c
 * @ description: implementation for the SHA512, SHA384, SHA512/224, SHA512/256, SHA512/t Secure Hash Algorithm
 * @      author: Gu Yongqiang
 * @        blog: https://blog.csdn.net/guyongqiangx
 */
#include <stdio.h>
#include <string.h>

#include "utils.h"
#include "sha512.h"

// #define DEBUG

#ifdef DEBUG
#define DBG(...) printf(__VA_ARGS__)
#define DUMP_BLOCK_DATA 1
#define DUMP_BLOCK_HASH 1
#define DUMP_ROUND_DATA 0
#else
#define DBG(...)
#define DUMP_BLOCK_DATA 0
#define DUMP_BLOCK_HASH 0
#define DUMP_ROUND_DATA 0
#endif

#define SHA512_BLOCK_SIZE           128 /* 1024 bits = 128 bytes */
#define SHA512_LEN_SIZE             16  /*  128 bits =  16 bytes */
#define SHA512_LEN_OFFSET           (SHA512_BLOCK_SIZE - SHA512_LEN_SIZE)
#define SHA512_DIGEST_SIZE          64  /*  512 bits =  64 bytes */

#define SHA512_PADDING_PATTERN      0x80
#define SHA512_ROUND_NUM            80

#define SHA384_DIGEST_SIZE          48  /*  384 bits =  48 bytes */
#define SHA512_224_DIGEST_SIZE      28  /*  224 bits =  28 bytes */
#define SHA512_256_DIGEST_SIZE      32  /*  256 bits =  32 bytes */

#define HASH_BLOCK_SIZE             SHA512_BLOCK_SIZE
#define HASH_LEN_SIZE               SHA512_LEN_SIZE
#define HASH_LEN_OFFSET             SHA512_LEN_OFFSET

#define HASH_DIGEST_SIZE            SHA512_DIGEST_SIZE

#define HASH_PADDING_PATTERN        SHA512_PADDING_PATTERN
#define HASH_ROUND_NUM              SHA512_ROUND_NUM

/* SHA512 Constants */
static const uint64_t K512[HASH_ROUND_NUM] =
{
    0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
    0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
    0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
    0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
    0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
    0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
    0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
    0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
    0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
    0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
    0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
    0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
    0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
    0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
    0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
    0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
    0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
    0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
    0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
};

/* ROTate Right (cirular right shift) */
static uint64_t ROTR(uint64_t x, uint8_t shift)
{
    return (x >> shift) | (x << (64 - shift));
}

/* Right SHift */
static uint64_t SHR(uint64_t x, uint8_t shift)
{
    return (x >> shift);
}

/* Ch ... choose */
static uint64_t Ch(uint64_t x, uint64_t y, uint64_t z)
{
    return (x & y) ^ (~x & z) ;
}

/* Maj ... majority */
static uint64_t Maj(uint64_t x, uint64_t y, uint64_t z)
{
    return (x & y) ^ (x & z) ^ (y & z);
}

/* SIGMA0 */
static uint64_t SIGMA0(uint64_t x)
{
    return ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39);
}

/* SIGMA1 */
static uint64_t SIGMA1(uint64_t x)
{
    return ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41);
}

/* sigma0, different from SIGMA0 */
static uint64_t sigma0(uint64_t x)
{
    return ROTR(x, 1) ^ ROTR(x, 8) ^ SHR(x, 7);
}

/* sigma1, different from SIGMA1 */
static uint64_t sigma1(uint64_t x)
{
    return ROTR(x, 19) ^ ROTR(x, 61) ^ SHR(x, 6);
}

int SHA512_Init(SHA512_CTX *c)
{
    if (NULL == c)
    {
        return ERR_INV_PARAM;
    }

    memset(c, 0, sizeof(SHA512_CTX));

    /* Initial Value for SHA512 */
    c->hash.a = 0x6a09e667f3bcc908;
    c->hash.b = 0xbb67ae8584caa73b;
    c->hash.c = 0x3c6ef372fe94f82b;
    c->hash.d = 0xa54ff53a5f1d36f1;
    c->hash.e = 0x510e527fade682d1;
    c->hash.f = 0x9b05688c2b3e6c1f;
    c->hash.g = 0x1f83d9abfb41bd6b;
    c->hash.h = 0x5be0cd19137e2179;

    c->total.low = 0;
    c->total.high = 0;
    c->last.used = 0;

    return ERR_OK;
}

static int SHA512_PrepareScheduleWord(const uint64_t *block, uint64_t *W)
{
    uint32_t t;

    if ((NULL == block) || (NULL == W))
    {
        return ERR_INV_PARAM;
    }

    for (t=0; t<HASH_ROUND_NUM; t++)
    {
        if (t<=15)  /*  0 <= t <= 15 */
            W[t] = be64toh(block[t]);
        else        /* 16 <= t <= 79 */
            W[t] = sigma1(W[t-2]) + W[t-7] + sigma0(W[t-15]) + W[t-16];
    }

    return ERR_OK;
}

static int SHA512_UpdateTotal(uint128_t *x, uint64_t len)
{
    uint64_t l;

    l = (x->low + (((uint64_t)len)<<3)) & 0xffffffffffffffff;
    if (l < x->low)
        x->high++;

    //if (sizeof(len) >= 8)
    //  x->high += ((uint64_t)len)>> 61)

    x->low = l;

    return ERR_OK;
}

#if 0
static int SHA512_SaveTotal(uint64_t *buffer, uint128_t *len)
{
    buffer[0] = htobe64(len->high);
    buffer[1] = htobe64(len->low);

    return ERR_OK;
}
#endif

#if (DUMP_BLOCK_DATA == 1)
static int SHA512_GetBlockCount(SHA512_CTX *ctx, uint128_t * count)
{
    if (ctx->total.high == 0)
    {
        count->low = ctx->total.low >> 10;
        count->high = 0;
    }
    else
    {
        count->low = ctx->total.low >> 10;
        count->low |= (ctx->total.high & 0x07FF << 54);
        count->high = ctx->total.high >> 10;
    }

    return ERR_OK;
}
#endif

static int SHA512_ProcessBlock(SHA512_CTX *ctx, const void *block)
{
    uint32_t t;
    uint64_t W[HASH_ROUND_NUM];
    uint64_t T1, T2;
    uint64_t a, b, c, d, e, f, g, h;

    if ((NULL == ctx) || (NULL == block))
    {
        return ERR_INV_PARAM;
    }

#if (DUMP_BLOCK_DATA == 1)
    DBG("---------------------------------------------------------\n");
    {
        uint128_t count;
        SHA512_GetBlockCount(ctx, &count);
        if (count.high == 0)
        {
            DBG("   BLOCK: %llu\n", count.low);
        }
        else{
            DBG("   BLOCK: %llu%016llu\n", count.high, count.low);
        }
    }
    DBG("    DATA:\n");
    print_buffer(block, HASH_BLOCK_SIZE, "    ");
#endif

    /* prepare schedule word */
    SHA512_PrepareScheduleWord(block, W);

    a = ctx->hash.a;
    b = ctx->hash.b;
    c = ctx->hash.c;
    d = ctx->hash.d;
    e = ctx->hash.e;
    f = ctx->hash.f;
    g = ctx->hash.g;
    h = ctx->hash.h;

#if (DUMP_BLOCK_HASH == 1)
    DBG("      IV: %016llx %016llx %016llx %016llx\n"
        "          %016llx %016llx %016llx %016llx\n", \
        ctx->hash.a, ctx->hash.b, ctx->hash.c, ctx->hash.d, ctx->hash.e, ctx->hash.f, ctx->hash.g, ctx->hash.h);
#endif

    for (t=0; t<HASH_ROUND_NUM; t++)
    {
        T1 = h + SIGMA1(e) + Ch(e, f, g) + K512[t] + W[t];
        T2 = SIGMA0(a) + Maj(a, b, c);
         h = g;
         g = f;
         f = e;
         e = d + T1;
         d = c;
         c = b;
         b = a;
         a = T1 + T2;

#if (DUMP_ROUND_DATA == 1)
        DBG("      %02d: T1=0x%016llx, T2=0x%016llx, W=0x%016llx\n", t, T1, T2, W[t]);
        DBG("           a=0x%016llx,  b=0x%016llx, c=0x%016llx, d=0x%016llx,\n"
            "           e=0x%016llx,  f=0x%016llx, g=0x%016llx, h=0x%016llx\n",
            a, b, c, d, e, f, g, h);
#endif
    }

    ctx->hash.a += a;
    ctx->hash.b += b;
    ctx->hash.c += c;
    ctx->hash.d += d;
    ctx->hash.e += e;
    ctx->hash.f += f;
    ctx->hash.g += g;
    ctx->hash.h += h;

#if (DUMP_BLOCK_HASH == 1)
    DBG("    HASH: %016llx %016llx %016llx %016llx\n"
        "          %016llx %016llx %016llx %016llx\n",
        ctx->hash.a, ctx->hash.b, ctx->hash.c, ctx->hash.d, ctx->hash.e, ctx->hash.f, ctx->hash.g, ctx->hash.h);
#endif

    return ERR_OK;
}

int SHA512_Update(SHA512_CTX *c, const void *data, size_t len)
{
    uint64_t copy_len = 0;

    if ((NULL == c) || (NULL == data))
    {
        return ERR_INV_PARAM;
    }

    /* has used data */
    if (c->last.used != 0)
    {
        /* less than 1 block in total, combine data */
        if (c->last.used + len < HASH_BLOCK_SIZE)
        {
            memcpy(&c->last.buf[c->last.used], data, len);
            c->last.used += len;

            return ERR_OK;
        }
        else /* more than 1 block */
        {
            /* process the block in context buffer */
            copy_len = HASH_BLOCK_SIZE - c->last.used;
            memcpy(&c->last.buf[c->last.used], data, copy_len);
            SHA512_ProcessBlock(c, &c->last.buf);
            SHA512_UpdateTotal(&c->total, HASH_BLOCK_SIZE);

            data = (uint8_t *)data + copy_len;
            len -= copy_len;

            /* reset context buffer */
            memset(&c->last.buf[0], 0, HASH_BLOCK_SIZE);
            c->last.used = 0;
        }
    }

    /* less than 1 block, copy to context buffer */
    if (len < HASH_BLOCK_SIZE)
    {
        memcpy(&c->last.buf[c->last.used], data, len);
        c->last.used += len;

        return ERR_OK;
    }
    else
    {
        /* process data blocks */
        while (len >= HASH_BLOCK_SIZE)
        {
            SHA512_ProcessBlock(c, data);
            SHA512_UpdateTotal(&c->total, HASH_BLOCK_SIZE);

            data = (uint8_t *)data + HASH_BLOCK_SIZE;
            len -= HASH_BLOCK_SIZE;
        }

        /* copy rest data to context buffer */
        memcpy(&c->last.buf[0], data, len);
        c->last.used = len;
    }

    return ERR_OK;
}

int SHA512_Final(unsigned char *md, SHA512_CTX *c)
{
    uint64_t *temp;

    if ((NULL == c) || (NULL == md))
    {
        return ERR_INV_PARAM;
    }

    /* Last block should be less thant HASH_BLOCK_SIZE - HASH_LEN_SIZE */
    if (c->last.used >= (HASH_BLOCK_SIZE - HASH_LEN_SIZE))
    {
        SHA512_UpdateTotal(&c->total, c->last.used);

        /* one more block */
        c->last.buf[c->last.used] = HASH_PADDING_PATTERN;
        c->last.used++;

        memset(&c->last.buf[c->last.used], 0, HASH_BLOCK_SIZE - c->last.used);
        SHA512_ProcessBlock(c, &c->last.buf);

        c->last.used = 0;

        memset(&c->last.buf[0], 0, HASH_BLOCK_SIZE - HASH_LEN_SIZE);

        // SHA512_SaveTotal(&c->last.buf[HASH_LEN_OFFSET], &c->total);
        temp = (uint64_t *)&(c->last.buf[HASH_LEN_OFFSET]);
        temp[0] = htobe64(c->total.high);
        temp[1] = htobe64(c->total.low);

        SHA512_ProcessBlock(c, &c->last.buf);
    }
    else /* 0 <= last.used < HASH_BLOCK_SIZE - HASH_LEN_SIZE */
    {
        SHA512_UpdateTotal(&c->total, c->last.used);

        /* one more block */
        c->last.buf[c->last.used] = HASH_PADDING_PATTERN;
        c->last.used++;

        /* padding 0s */
        memset(&c->last.buf[c->last.used], 0, HASH_BLOCK_SIZE - HASH_LEN_SIZE - c->last.used);

        // SHA512_SaveTotal(&c->last.buf[HASH_LEN_OFFSET], &c->total);
        temp = (uint64_t *)&(c->last.buf[HASH_LEN_OFFSET]);
        temp[0] = htobe64(c->total.high);
        temp[1] = htobe64(c->total.low);

        SHA512_ProcessBlock(c, &c->last.buf);
    }

    temp = (uint64_t *)md;
    temp[0] = htobe64(c->hash.a);
    temp[1] = htobe64(c->hash.b);
    temp[2] = htobe64(c->hash.c);
    temp[3] = htobe64(c->hash.d);
    temp[4] = htobe64(c->hash.e);
    temp[5] = htobe64(c->hash.f);
    temp[6] = htobe64(c->hash.g);
    temp[7] = htobe64(c->hash.h);

    return ERR_OK;
}

unsigned char *SHA512(const unsigned char *d, size_t n, unsigned char *md)
{
    SHA512_CTX c;

    if ((NULL == d) || (NULL == md))
    {
        return NULL;
    }

    SHA512_Init(&c);
    SHA512_Update(&c, d, n);
    SHA512_Final(md, &c);

    return md;
}

static int SHA512_xxx_Final(unsigned char *md, unsigned int md_size, SHA512_CTX *c)
{
    int rc = ERR_OK;
    unsigned char sha512_md[SHA512_DIGEST_SIZE];

    memset(&sha512_md, 0, sizeof(sha512_md));

    rc = SHA512_Final(sha512_md, c);

    memcpy(md, sha512_md, md_size);

    return rc;
}

int SHA384_Init(SHA512_CTX *c)
{
    if (NULL == c)
    {
        return ERR_INV_PARAM;
    }

    memset(c, 0, sizeof(SHA512_CTX));

    /* Initial Value for SHA384 */
    c->hash.a = 0xcbbb9d5dc1059ed8;
    c->hash.b = 0x629a292a367cd507;
    c->hash.c = 0x9159015a3070dd17;
    c->hash.d = 0x152fecd8f70e5939;
    c->hash.e = 0x67332667ffc00b31;
    c->hash.f = 0x8eb44a8768581511;
    c->hash.g = 0xdb0c2e0d64f98fa7;
    c->hash.h = 0x47b5481dbefa4fa4;

    c->total.low = 0;
    c->total.high = 0;
    c->last.used = 0;

    return ERR_OK;
}

int SHA384_Update(SHA512_CTX *c, const void *data, size_t len)
{
    return SHA512_Update(c, data, len);
}

int SHA384_Final(unsigned char *md, SHA512_CTX *c)
{
    return SHA512_xxx_Final(md, SHA384_DIGEST_SIZE, c);
}

unsigned char *SHA384(const unsigned char *d, size_t n, unsigned char *md)
{
    SHA512_CTX c;

    if ((NULL == d) || (NULL == md))
    {
        return NULL;
    }

    SHA384_Init(&c);
    SHA384_Update(&c, d, n);
    SHA384_Final(md, &c);

    return md;
}

int SHA512_224_Init(SHA512_CTX *c)
{
    if (NULL == c)
    {
        return ERR_INV_PARAM;
    }

    memset(c, 0, sizeof(SHA512_CTX));

    /* Initial Value for SHA512/224 */
    c->hash.a = 0x8c3d37c819544da2;
    c->hash.b = 0x73e1996689dcd4d6;
    c->hash.c = 0x1dfab7ae32ff9c82;
    c->hash.d = 0x679dd514582f9fcf;
    c->hash.e = 0x0f6d2b697bd44da8;
    c->hash.f = 0x77e36f7304c48942;
    c->hash.g = 0x3f9d85a86a1d36c8;
    c->hash.h = 0x1112e6ad91d692a1;

    c->total.low = 0;
    c->total.high = 0;
    c->last.used = 0;

    return ERR_OK;
}

int SHA512_224_Update(SHA512_CTX *c, const void *data, size_t len)
{
    return SHA512_Update(c, data, len);
}

int SHA512_224_Final(unsigned char *md, SHA512_CTX *c)
{
    return SHA512_xxx_Final(md, SHA512_224_DIGEST_SIZE, c);
}

unsigned char *SHA512_224(const unsigned char *d, size_t n, unsigned char *md)
{
    SHA512_CTX c;

    if ((NULL == d) || (NULL == md))
    {
        return NULL;
    }

    SHA512_224_Init(&c);
    SHA512_224_Update(&c, d, n);
    SHA512_224_Final(md, &c);

    return md;
}

int SHA512_256_Init(SHA512_CTX *c)
{
    if (NULL == c)
    {
        return ERR_INV_PARAM;
    }

    memset(c, 0, sizeof(SHA512_CTX));

    /* Initial Value for SHA512/256 */
    c->hash.a = 0x22312194fc2bf72c;
    c->hash.b = 0x9f555fa3c84c64c2;
    c->hash.c = 0x2393b86b6f53b151;
    c->hash.d = 0x963877195940eabd;
    c->hash.e = 0x96283ee2a88effe3;
    c->hash.f = 0xbe5e1e2553863992;
    c->hash.g = 0x2b0199fc2c85b8aa;
    c->hash.h = 0x0eb72ddc81c52ca2;

    c->total.low = 0;
    c->total.high = 0;
    c->last.used = 0;

    return ERR_OK;
}

int SHA512_256_Update(SHA512_CTX *c, const void *data, size_t len)
{
    return SHA512_Update(c, data, len);
}

int SHA512_256_Final(unsigned char *md, SHA512_CTX *c)
{
    return SHA512_xxx_Final(md, SHA512_256_DIGEST_SIZE, c);
}

unsigned char *SHA512_256(const unsigned char *d, size_t n, unsigned char *md)
{
    SHA512_CTX c;

    if ((NULL == d) || (NULL == md))
    {
        return NULL;
    }

    SHA512_256_Init(&c);
    SHA512_256_Update(&c, d, n);
    SHA512_256_Final(md, &c);

    return md;
}

static int SHA512t_GenerateIV(SHA512_CTX *c, unsigned int t)
{
    char name[12]; /* 12 chars for "SHA-512/xxx", like "SHA512/224" */
    unsigned char md[SHA512_DIGEST_SIZE];

    SHA512_Init(c);

    c->hash.a ^= 0xa5a5a5a5a5a5a5a5;
    c->hash.b ^= 0xa5a5a5a5a5a5a5a5;
    c->hash.c ^= 0xa5a5a5a5a5a5a5a5;
    c->hash.d ^= 0xa5a5a5a5a5a5a5a5;
    c->hash.e ^= 0xa5a5a5a5a5a5a5a5;
    c->hash.f ^= 0xa5a5a5a5a5a5a5a5;
    c->hash.g ^= 0xa5a5a5a5a5a5a5a5;
    c->hash.h ^= 0xa5a5a5a5a5a5a5a5;

    /* "SHA-512/xxx" */
    memset(name, 0, sizeof(name));
    sprintf(name, "SHA-512/%d", t);

    SHA512_Update(c, name, strlen(name));
    SHA512_Final(md, c);

#if (DUMP_BLOCK_HASH == 1)
    DBG("      IV: (%s)\n", name);
    DBG("          %016llx %016llx %016llx %016llx\n"
        "          %016llx %016llx %016llx %016llx\n", \
        c->hash.a, c->hash.b, c->hash.c, c->hash.d, c->hash.e, c->hash.f, c->hash.g, c->hash.h);
#endif

    c->ext = t;

    return ERR_OK;
}

int SHA512t_Init(SHA512_CTX *c, unsigned int t)
{
    if (NULL == c)
    {
        return ERR_INV_PARAM;
    }

    /* t=8x, t!=0, t!=384, t!=512 */
    if (( t >= 512) || (0 != t%8) || (384 == t) || (0 == t))
    {
        return ERR_INV_PARAM;
    }

    memset(c, 0, sizeof(SHA512_CTX));

    /* Generate Initial Value for SHA512/t */
    SHA512t_GenerateIV(c, t);

    c->total.low = 0;
    c->total.high = 0;
    c->last.used = 0;

    return ERR_OK;
}

int SHA512t_Update(SHA512_CTX *c, const void *data, size_t len)
{
    return SHA512_Update(c, data, len);
}

int SHA512t_Final(unsigned char *md, SHA512_CTX *c)
{
    return SHA512_xxx_Final(md, c->ext/8, c);
}

unsigned char *SHA512t(const unsigned char *d, size_t n, unsigned char *md, unsigned int t)
{
    SHA512_CTX c;

    if ((NULL == d) || (NULL == md))
    {
        return NULL;
    }
    if ((t > 512) || (t%8 != 0) || (t == 0) || (t == 384))
    {
        return NULL;
    }

    SHA512t_Init(&c, t);
    SHA512t_Update(&c, d, n);
    SHA512t_Final(md, &c);

    return md;
}

从上面的实现来看,SHA-384, SHA-512/224, SHA-512/256, SHA-512/t和SHA512的主要区别在于:

  • SHA-384, SHA-512/224, SHA-512/256, SHA-512/t初始化函数中的初始化常量不一致;
  • SHA-384, SHA-512/224, SHA-512/256, SHA-512/t哈希结果中,从基于SHA512得到的哈希中截取前面部分作为这几个函数的哈希值
  • SHA-512/t的初始化常量是通过t值计算的,不同的t值,计算得到的初始化常量不一样

SHA512源码的编译和测试

我直接在Makefile中内置了一个test伪目标,编译时除了编译生成名为sha512的哈希工具外,还会直接调用内置的哈希测试。

编译和运行如下:

$ make
gcc -Wall -g -O2 -c utils.c -o utils.o
gcc -Wall -g -O2 -c sha512.c -o sha512.o
gcc -Wall -g -O2 -c sha512test.c -o sha512test.o
gcc -Wall -g -O2 utils.o sha512.o sha512test.o -o sha512

Run Test...
./sha512 -a sha384 -x
Internal hash tests for ./sha512(SHA384):
./sha512("")
  Expect: 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b
  Result: 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b

./sha512("a")
  Expect: 54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31
  Result: 54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31

./sha512("abc")
  Expect: cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7
  Result: cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7

./sha512("message digest")
  Expect: 473ed35167ec1f5d8e550368a3db39be54639f828868e9454c239fc8b52e3c61dbd0d8b4de1390c256dcbb5d5fd99cd5
  Result: 473ed35167ec1f5d8e550368a3db39be54639f828868e9454c239fc8b52e3c61dbd0d8b4de1390c256dcbb5d5fd99cd5

./sha512("abcdefghijklmnopqrstuvwxyz")
  Expect: feb67349df3db6f5924815d6c3dc133f091809213731fe5c7b5f4999e463479ff2877f5f2936fa63bb43784b12f3ebb4
  Result: feb67349df3db6f5924815d6c3dc133f091809213731fe5c7b5f4999e463479ff2877f5f2936fa63bb43784b12f3ebb4

./sha512("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
  Expect: 1761336e3f7cbfe51deb137f026f89e01a448e3b1fafa64039c1464ee8732f11a5341a6f41e0c202294736ed64db1a84
  Result: 1761336e3f7cbfe51deb137f026f89e01a448e3b1fafa64039c1464ee8732f11a5341a6f41e0c202294736ed64db1a84

./sha512("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
  Expect: b12932b0627d1c060942f5447764155655bd4da0c9afa6dd9b9ef53129af1b8fb0195996d2de9ca0df9d821ffee67026
  Result: b12932b0627d1c060942f5447764155655bd4da0c9afa6dd9b9ef53129af1b8fb0195996d2de9ca0df9d821ffee67026

./sha512 -a sha512 -x
Internal hash tests for ./sha512(SHA512):
./sha512("")
  Expect: cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
  Result: cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e

./sha512("a")
  Expect: 1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75
  Result: 1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75

./sha512("abc")
  Expect: ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f
  Result: ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f

./sha512("message digest")
  Expect: 107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c
  Result: 107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c

./sha512("abcdefghijklmnopqrstuvwxyz")
  Expect: 4dbff86cc2ca1bae1e16468a05cb9881c97f1753bce3619034898faa1aabe429955a1bf8ec483d7421fe3c1646613a59ed5441fb0f321389f77f48a879c7b1f1
  Result: 4dbff86cc2ca1bae1e16468a05cb9881c97f1753bce3619034898faa1aabe429955a1bf8ec483d7421fe3c1646613a59ed5441fb0f321389f77f48a879c7b1f1

./sha512("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
  Expect: 1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894
  Result: 1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894

./sha512("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
  Expect: 72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d1914042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843
  Result: 72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d1914042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843

./sha512 -a sha512-224 -x
Internal hash tests for ./sha512(SHA512/224):
./sha512("")
  Expect: 6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4
  Result: 6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4

./sha512("a")
  Expect: d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327
  Result: d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327

./sha512("abc")
  Expect: 4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa
  Result: 4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa

./sha512("message digest")
  Expect: ad1a4db188fe57064f4f24609d2a83cd0afb9b398eb2fcaeaae2c564
  Result: ad1a4db188fe57064f4f24609d2a83cd0afb9b398eb2fcaeaae2c564

./sha512("abcdefghijklmnopqrstuvwxyz")
  Expect: ff83148aa07ec30655c1b40aff86141c0215fe2a54f767d3f38743d8
  Result: ff83148aa07ec30655c1b40aff86141c0215fe2a54f767d3f38743d8

./sha512("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
  Expect: a8b4b9174b99ffc67d6f49be9981587b96441051e16e6dd036b140d3
  Result: a8b4b9174b99ffc67d6f49be9981587b96441051e16e6dd036b140d3

./sha512("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
  Expect: ae988faaa47e401a45f704d1272d99702458fea2ddc6582827556dd2
  Result: ae988faaa47e401a45f704d1272d99702458fea2ddc6582827556dd2

./sha512 -a sha512-256 -x
Internal hash tests for ./sha512(SHA512/256):
./sha512("")
  Expect: c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a
  Result: c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a

./sha512("a")
  Expect: 455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8
  Result: 455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8

./sha512("abc")
  Expect: 53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23
  Result: 53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23

./sha512("message digest")
  Expect: 0cf471fd17ed69d990daf3433c89b16d63dec1bb9cb42a6094604ee5d7b4e9fb
  Result: 0cf471fd17ed69d990daf3433c89b16d63dec1bb9cb42a6094604ee5d7b4e9fb

./sha512("abcdefghijklmnopqrstuvwxyz")
  Expect: fc3189443f9c268f626aea08a756abe7b726b05f701cb08222312ccfd6710a26
  Result: fc3189443f9c268f626aea08a756abe7b726b05f701cb08222312ccfd6710a26

./sha512("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
  Expect: cdf1cc0effe26ecc0c13758f7b4a48e000615df241284185c39eb05d355bb9c8
  Result: cdf1cc0effe26ecc0c13758f7b4a48e000615df241284185c39eb05d355bb9c8

./sha512("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
  Expect: 2c9fdbc0c90bdd87612ee8455474f9044850241dc105b1e8b94b8ddf5fac9148
  Result: 2c9fdbc0c90bdd87612ee8455474f9044850241dc105b1e8b94b8ddf5fac9148

./sha512 -a sha512t -t 224 -x
Internal hash tests for ./sha512(SHA512/t):
./sha512("")
  Expect: 6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4
  Result: 6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4

./sha512("a")
  Expect: d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327
  Result: d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327

./sha512("abc")
  Expect: 4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa
  Result: 4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa

./sha512("message digest")
  Expect: ad1a4db188fe57064f4f24609d2a83cd0afb9b398eb2fcaeaae2c564
  Result: ad1a4db188fe57064f4f24609d2a83cd0afb9b398eb2fcaeaae2c564

./sha512("abcdefghijklmnopqrstuvwxyz")
  Expect: ff83148aa07ec30655c1b40aff86141c0215fe2a54f767d3f38743d8
  Result: ff83148aa07ec30655c1b40aff86141c0215fe2a54f767d3f38743d8

./sha512("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
  Expect: a8b4b9174b99ffc67d6f49be9981587b96441051e16e6dd036b140d3
  Result: a8b4b9174b99ffc67d6f49be9981587b96441051e16e6dd036b140d3

./sha512("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
  Expect: ae988faaa47e401a45f704d1272d99702458fea2ddc6582827556dd2
  Result: ae988faaa47e401a45f704d1272d99702458fea2ddc6582827556dd2

./sha512 -a sha384 -f sha512
./sha512(sha512) = 1b52191008c0e0094f0a9ab06227cdfed06863451013263c178c2a70839096a267ee7fc91de63893290a29e480835d28
./sha512 -a sha512 -f sha512
./sha512(sha512) = 3c451ef72cfbb060fb041bec35b0c5738b98ff01dace09794a8a095b5219036f88ad6e5bceba51a624c5889f65d3605a9920a07d4ce8e37b4d9d161e5bd94634
./sha512 -a sha512-224 -f sha512
./sha512(sha512) = 637747ac3951b51946bb80aab9be4951999a0e8a69c033567fc8d5ec
./sha512 -a sha512-256 -f sha512
./sha512(sha512) = 2265caf9c5ef1022ab306501269209561fab94e22efd32df20c3b74a6351547d
./sha512 -a sha512t -t 224 -f sha512
./sha512(sha512) = 637747ac3951b51946bb80aab9be4951999a0e8a69c033567fc8d5ec

最新版本的openssl工具已经支持SHA512系列的哈希算法(SHA-512/t除外),因此可以将sha512工具和openssl执行dgst计算的结果进行比较:

$ sha512 -h
Usage:
Common options: [-x|-f file|-s string| -a sha384|sha512|sha512-224|sha512-256|sha512t | -t num | -h]
Hash a string:
        sha512 -a sha384|sha512|sha512-224|sha512-256|sha512t -s string
Hash a file:
        sha512 -a sha384|sha512|sha512-224|sha512-256|sha512t -f file
-a      Secure hash algorithm: "sha384", "sha512", "sha512-224", "sha512-256"
-t      t value for SHA512/t, positive integer without a leading zero, (0<t<512, t/8=0, t!=384)
-x      Internal string hash test
-h      Display this message

#
# 使用sha512工具分别对文件和字符串计算哈希值
#

$ for h in sha384 sha512 sha512-224 sha512-256; \
  do \
    echo "sha512 -a $h -f sha512.o"; \
    sha512 -a $h -f sha512.o; \
  done;
sha512 -a sha384 -f sha512.o
sha512(sha512.o) = 46ebdfd95d7b5819d265ab9c1abe4e77409b59920cee91715e8e75728acc30c7f11e6f2680fedfaed13ddc78b3f9269a
sha512 -a sha512 -f sha512.o
sha512(sha512.o) = 815eab41765653993db44ebe013d496b0fd0d9649f8f88a3d859a507fbcc31f0c24de18514b513ff14bf6508e54f6527da49e627585dc6f8b81a97653020c310
sha512 -a sha512-224 -f sha512.o
sha512(sha512.o) = 999b0a92bd6a8559d3eb4921403c9d3132a1454ef3b1e60f37c17432
sha512 -a sha512-256 -f sha512.o
sha512(sha512.o) = 0e4f315c0af0e2e25e23bdf866338e65dc5885f9e90fa0484d1d1410f24a94b0

$ S="I Love China!"; \
  for h in sha384 sha512 sha512-224 sha512-256; \
  do \
    echo "sha512 -a $h -s \"$S\""; \
    sha512 -a $h -s $S; \
  done;
sha512 -a sha384 -s "I Love China!"
sha512("I") = 54738b3c22eb17fa6f32dce8ae4c2bbf474ac7d89cf3aad01490246c943579ef28e6537f948eab03e5b8ecece20f0683
sha512 -a sha512 -s "I Love China!"
sha512("I") = 32b1786eca2b9f815b4c52b999e2b34dae877a86c00e8f745e7ac23388665c0703a947085bd7f975c5210ffab9b5a8f3931ab40b26cd7bccc4d7690cd19a4277
sha512 -a sha512-224 -s "I Love China!"
sha512("I") = 38f27409e58691cbd80c0c6258117446b53e75a9895827ddbd85f53a
sha512 -a sha512-256 -s "I Love China!"
sha512("I") = 7cdd023a2896a7c29338a40033fe9b711678861c5b43eaa36347ecc35d25d1c0

#
# 使用开源的openssl工具计算相应的哈希进行对比
#
$ for h in sha384 sha512 sha512-224 sha512-256; \
  do \
    echo "openssl dgst -$h sha512.o"; \
    openssl dgst -$h sha512.o; \
  done;
openssl dgst -sha384 sha512.o
SHA384(sha512.o)= 46ebdfd95d7b5819d265ab9c1abe4e77409b59920cee91715e8e75728acc30c7f11e6f2680fedfaed13ddc78b3f9269a
openssl dgst -sha512 sha512.o
SHA512(sha512.o)= 815eab41765653993db44ebe013d496b0fd0d9649f8f88a3d859a507fbcc31f0c24de18514b513ff14bf6508e54f6527da49e627585dc6f8b81a97653020c310
openssl dgst -sha512-224 sha512.o
SHA512-224(sha512.o)= 999b0a92bd6a8559d3eb4921403c9d3132a1454ef3b1e60f37c17432
openssl dgst -sha512-256 sha512.o
SHA512-256(sha512.o)= 0e4f315c0af0e2e25e23bdf866338e65dc5885f9e90fa0484d1d1410f24a94b0

$ S="I Love China!"; \
  for h in sha384 sha512 sha512-224 sha512-256; \
  do \
    echo "echo -n \"$S\" | openssl dgst -$h"; \
    echo -n \"$S\" | openssl dgst -$h; \
  done;
echo -n "I Love China!" | openssl dgst -sha384
(stdin)= 10e849acdc83e320de1d092a0a582e3af1055bd55150c7d81a88d52145241b686ac0a9ef713a6da8a9e73e5af33c2355
echo -n "I Love China!" | openssl dgst -sha512
(stdin)= fbd797a0ef6fd8cfdb9e5efffbc24eac2e60f4193440ee58a5608a2aff96dab1c22435e05fcc6e018a5a9e4e0827ef670f600e22da028ff2fd9c29d0843aa945
echo -n "I Love China!" | openssl dgst -sha512-224
(stdin)= ecd4de998d134d52ce6c379b9bd7dcd2d4405873b593cdb2ece3ab7c
echo -n "I Love China!" | openssl dgst -sha512-256
(stdin)= 159369bde3fb15b7d5e20a163e66e43601be5eff963b8c554dba114c54f78017

完整代码

完整的代码文件列表如下:

sha512$ ls -lh
total 60K
-rwxr-xr-x 1 rg935739 stb_all  942 Jun 22 17:20 Makefile
-rwxr-xr-x 1 rg935739 stb_all  18K Jun 22 18:21 sha512.c
-rwxr-xr-x 1 rg935739 stb_all 2.6K Jun 21 09:48 sha512.h
-rwxr-xr-x 1 rg935739 stb_all  22K Jun 22 18:06 sha512test.c
-rwxr-xr-x 1 rg935739 stb_all  758 Jun 21 09:48 utils.c
-rwxr-xr-x 1 rg935739 stb_all 1.8K Jun 21 09:48 utils.h

需要代码请访问:

  • https://github.com/guyongqiangx/cryptography/

其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

  • 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
  • 一个Android OTA的讨论组,请说明加Android OTA群。
  • 一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

收钱码

洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

公众号

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洛奇看世界

一分也是爱~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值