需求
假设一款Armv8-A架构的芯片,由于没有硬件密码算法引擎,如何实现对SHA256算法加速。
方案
Arm Neon 技术,一种⾼级 SIMD(Single Instruction Multiple Data,一条指令操作多个数据)架构扩展,Armv8‑A 和 Armv8-R 架构均支持 Neon 技术扩展。使用 Neon 技术有多种方法:
- 支持 Neon 的开源库,例如 Arm Compute Library
- 编译器的自动矢量化特性可以利用 Neon 技术自动优化你的代码
- Neon intrinsics内建函数,编译器用相应的 Neon 指令进行了封装
- 对于经验丰富的程序员来说,为了获得极佳的性能,手动编写 Neon 汇编也是一种方法
加密扩展CE(Cryptographic Extension)是 Neon 其中的一种指令集,用于实现AES加解密和SHA哈希的加速运算。加速SHA256算法的Neon指令包括如下四种:
指令 | 功能 |
---|---|
SHA256H | SHA256 hash update (part 1) |
SHA256H2 | SHA256 hash update (part 2) |
SHA256SU0 | SHA256 schedule update 0 |
SHA256SU1 | SHA256 schedule update 1 |
这里我们采用 Neon intrinsics 实现 SHA256 算法的加速,SHA256 Intrinsics 指令如下:
本方案需要对SHA256算法原理,Neon技术等有一定的了解,详细可参考本公众号/博客的其他文章。
实现
sha256.h
SHA256算法需要实现分片哈希和一次性哈希的功能,包括如下函数:
函数 | 功能 |
---|---|
sha256_starts | SHA256开始,创建上下文 |
sha256_update | SHA256更新,输入消息数据 |
sha256_finish | SHA256结束,得到摘要值 |
sha256_compute | 计算一块数据的SHA256摘要值 |
上面前三个函数是分片计算哈希,最后一个函数是一次性计算哈希。新建一个sha256.h
文件,代码如下:
#ifndef _SHA256_H_
#define _SHA256_H_
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C"{
#endif
struct sha256_ctx
{
uint32_t total[2]; /* 处理过的数据长度 */
uint32_t state[8]; /* 中间摘要状态 */
uint8_t buffer[64]; /* 正在处理的数据块 */
};
typedef struct sha256_ctx sha256_ctx_t;
/**
***********************************************************************************************************************
* @brief starts a sha256 context
*
* @param[in] ctx: the context
*
* @return 0: success, other: fail
***********************************************************************************************************************
*/
int sha256_starts(sha256_ctx_t *ctx);
/**
***********************************************************************************************************************
* @brief this function feeds an input buffer into an ongoing sha256 context
*
* @param[in] ctx: the context
* @param[in] input: the buffer holding the input data
* @param[in] ilen: the length of the input data
*
* @return 0: success, other: fail
***********************************************************************************************************************
*/
int sha256_update(sha256_ctx_t *ctx, const uint8_t *input, size_t ilen);
/**
***********************************************************************************************************************
* @brief the finalization function
*
* @param[in] ctx: the context
* @param[out] output: the buffer holding the output data
*
* @return 0: success, other: fail
***********************************************************************************************************************
*/
int sha256_finish(sha256_ctx_t *ctx, uint8_t output[32]);
/**
***********************************************************************************************************************
* @brief compute the sha256 checksum of a buffer
*
* @param[in] input: the buffer holding the input data
* @param[in] ilen: the length of the input data
* @param[out] output: the buffer holding the output data
*
* @return 0: success, other: fail
***********************************************************************************************************************
*/
int sha256_compute(const uint8_t *input, size_t ilen, uint8_t output[32]);
#ifdef __cplusplus
}
#endif
#endif /* _SHA256_H_ */
其中定义了一个sha256上下文结构体,用于分片哈希计算,参数如下:
- total:记录处理过的数据长度
- state:记录中间摘要状态值
- buffer:存放正在处理的数据块
sha256.c
新建一个sha256.c
文件,需要实现sha256.h
中定义的四个函数。
sha256_starts
sha256_starts
函数主要是创建上下文,并设置初始的哈希值。
int sha256_starts(sha256_ctx_t *ctx)
{
memset(ctx, 0, sizeof(sha256_ctx_t));
/* 清零 */
ctx->total[0] = 0;
ctx->total[1] = 0;
/* 初始哈希值 */
ctx->state[0] = 0x6a09e667;
ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372;
ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f;
ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab;
ctx->state[7] = 0x5be0cd19;
return 0;
}
sha256_update
sha256_update
函数主要是进行哈希更新计算,由于sha256内部每次处理512比特(64字节),因此需要保证每次哈希更新计算的长度为512比特的倍数。
int sha256_update(sha256_ctx_t *ctx, const uint8_t *input, size_t ilen)
{
size_t fill;
uint32_t left;
size_t block_num;
size_t block_len;
if (ilen == 0) {
return 0;
}
/* 上次剩余的消息长度 */
left = ctx->total[0] & 0x3F;
/* 需要填充fill长度消息,sha256每次处理512比特(即64字节) */
fill = 64 - left;
/* 更新已处理的消息长度 */
ctx->total[0] += (uint32_t)ilen;
ctx->total[0] &= 0xFFFFFFFF;
/* 产生进位,total[1]+1 */
if (ctx->total[0] < (uint32_t)ilen) {
ctx->total[1]++;
}
/* 先处理上一次剩余消息 */
if (left && ilen >= fill) {
/* 拷贝fill长度消息,使其为64字节 */
memcpy((void *)(ctx->buffer + left), input, fill);
sha256_internal_process(ctx->state, ctx->buffer, 1);
input += fill;
ilen -= fill;
left = 0;
}
/* 再处理输入消息 */
block_num = ilen >> 6;
block_len = block_num << 6;
if (block_num) {
sha256_internal_process(ctx->state, input, block_num);
input += block_len;
ilen -= block_len;
}
/* 剩余消息小于64字节,暂存 */
if (ilen > 0) {
memcpy((void *)(ctx->buffer + left), input, ilen);
}
return 0;
}
- 首先进行消息长度处理,与本次输入的消息长度相加
- 如果上次有消息数据未处理,先要拷贝填充至64字节,进行哈希计算
- 然后对本次的剩余消息按块计算哈希
- 最后如果本次最后一个块不足64字节,需要进行数据暂存
其中sha256_internal_process
是sha256的核心部分,实现内部的哈希调度计算,实现见下一节。
sha256_finish
sha256_finish
函数是完成哈希计算,得到消息的摘要值。
/* 双字->4字节,大端模式 */
#ifndef PUT_UINT32_BE
#define PUT_UINT32_BE( n, data, offset ) \
{
\
( data )[( offset ) ] = (uint8_t) ( (n) >> 24 ); \
( data )[( offset ) + 1] = (uint8_t) ( (n) >> 16 ); \
( data )[( offset ) + 2] = (uint8_t) ( (n) >> 8 ); \
( data )[( offset ) + 3] = (u