iOS-SM3加密算法N種集成

近期的一個項(xiàng)目需要用到SM3加密算法,需要在iOS中使用Objective-C實(shí)現(xiàn)SM3國密加密算法。

SM3:是中國國家密碼管理局發(fā)布的密碼雜湊算法標(biāo)準(zhǔn),適用于商用密碼應(yīng)用中的數(shù)字簽名和驗(yàn)證、消息認(rèn)證碼的生成與驗(yàn)證以及隨機(jī)數(shù)的生成等

由于iOS系統(tǒng)并未內(nèi)置SM3算法,我們需要使用第三方開源庫或自己實(shí)現(xiàn)

GMObjC庫: 是一個基于 OpenSSL 的國密(SM2、SM3、SM4)算法的 Objective-C 開源庫,適用于 iOS 和 macOS 開發(fā)。它封裝了中國國家密碼管理局發(fā)布的多種加密算法,包括:

1. SM2: 支持基于橢圓曲線(ECC)的加解密,密鑰協(xié)商(ECDH)和簽名算法

2. SM3: 類似 SHA 系列的國密哈希算法,包含 SM3 和 HMAC 等

3. SM4: 實(shí)現(xiàn)對稱分組加密算法

GmSSL庫:GmSSL是由北京大學(xué)自主開發(fā)的國產(chǎn)商用密碼開源庫,實(shí)現(xiàn)了對國密算法、標(biāo)準(zhǔn)和安全通信協(xié)議的全面功能覆蓋,支持包括移動端在內(nèi)的主流操作系統(tǒng)和處理器,支持密碼鑰匙、密碼卡等典型國產(chǎn)密碼硬件,提供功能豐富的命令行工具及多種編譯語言編程接口

方案一:使用第三方庫(GMObjC)

集成GMObjC:集成GMObjC方法

因?yàn)槲覀兊捻?xiàng)目是SDK不便用CocoaPods方法,因此我只能選擇直接集成和手動編譯為 Framework。

1.直接集成 (demo)

1.從 Git 下載最新代碼,找到和 README 同級的 GMObjC 文件夾,將 GMObjC 文件夾拖入項(xiàng)目

2.找到和 README 同級的 Frameworks 文件夾,將項(xiàng)目 Frameworks/OpenSSL.xcframework 拖入項(xiàng)目

3.在需要使用的地方導(dǎo)入頭文件 GMObjC.h 即可使用 SM2、SM4 加解密,簽名驗(yàn)簽,計算 SM3 摘要等

注意事項(xiàng)

GMObjC 依賴 OpenSSL,可直接拖入 Frameworks/OpenSSL.xcframework 或通過pod GMOpenSSL安裝 OpenSSL。

如果項(xiàng)目中已集成 OpenSSL 1.1.1l 以上版本,可共用同一個 OpenSSL;否則需要使用 Carthage 將 GMObjC 編譯為動態(tài)庫。

我按照以上步驟將文件導(dǎo)入后報錯:
OpenSSL.xcframework 簽名驗(yàn)證失敗

OpenSSL.xcframework報錯

終端執(zhí)行強(qiáng)制重簽名命令

codesign --force --deep --sign - 你的路徑/OpenSSL.xcframework

返回:你的路徑/OpenSSL.xcframework: replacing existing signature

現(xiàn)在就可以運(yùn)行測試了:

#import "GMObjC.h"

NSString *str = @"123@1234";
NSString *digest = [GMSm3Utils hashWithText:str];
NSLog(@"%@", digest);

2.手動編譯為 Framework (demo)

1.動態(tài)庫:

從 GitHub 下載源碼,打開項(xiàng)目GMObjC-master/Frameworks/GMObjC.xcframework把這個拖入項(xiàng)目

在 Xcode 的 General → Frameworks, Libraries, and Embedded Content 中需標(biāo)記為 Embed & Sign

Embed & Sign
#import "GMObjC/GMObjC.h"

NSString *digest1 = [GMSm3Utils hashWithText:str];
NSLog(@"%@", digest1);

2.靜態(tài)庫:

從 GitHub 下載源碼,打開項(xiàng)目 GMObjC.xcodeproj,設(shè)置 Build Settings - Linking-General - Mach-O Type 為 Static Library

手動編譯為靜態(tài)庫 GMObjC.framework

合并為 XCFramework:通過xcodebuild -create-xcframework命令來合并為 XCFramework,通過合并 GMObjC 庫的模擬器和真機(jī)版本來演示

# 創(chuàng)建合并包 GMObjC.xcframework

xcodebuild -create-xcframework \
           -framework Release-iphoneos/GMObjC.framework \
           -framework Release-iphonesimulator/GMObjC.framework \
           -output GMObjC.xcframework

把生成的GMObjC.xcframework拖入項(xiàng)目即可

3.CocoaPods安裝GMObjC (GMObjC-demo) (GMDynamic-demo)

GMObjC 和 GMDynamic 只能安裝其中一個,二者不能同時安裝。

GMObjC 為靜態(tài)庫,GMDynamic 為編譯好的 GMObjC 動態(tài)庫版本。

# 安裝 GMObjC 的源碼和 GMOpenSSL.xcframework (靜態(tài)庫)
pod 'GMObjC', '~> 4.0.3'

# 當(dāng) Podfile 中使用 use_frameworks! 時,安裝 GMObjC.xcframework (動態(tài)庫)
pod 'GMDynamic', '~> 4.0.3'

方案二:使用第三方庫(GmSSL)(demo)

集成GmSSL:

集成GmSSL方法

但是我用這種方法不行,我用了其他的方法。

我們使用GmSSL 3.x(master分支)來編譯iOS的靜態(tài)庫(libcrypto.a和libssl.a)。由于3.x版本采用了CMake構(gòu)建系統(tǒng),因此流程與2.x不同。

GmSSL 3.x 的構(gòu)建系統(tǒng)已經(jīng)發(fā)生了變化,生成的庫文件名為 libgmssl.a 而不是傳統(tǒng)的 libcrypto.a 和 libssl.a。

如果項(xiàng)目必須使用 libcrypto.alibssl.a,請回退到 GmSSL 2.x

  1. 克隆代碼并切換到master分支(或最新的穩(wěn)定標(biāo)簽)

  2. 配置CMake工具鏈文件(為iOS交叉編譯)

  3. 分別編譯arm64(真機(jī))和x86_64(模擬器)架構(gòu)

  4. 使用lipo合并成通用靜態(tài)庫

  5. 將生成的靜態(tài)庫和頭文件集成到iOS項(xiàng)目中。

創(chuàng)建編譯腳本: build_ios.sh(放在GmSSL根目錄)

#!/bin/bash
set -e

# 確保使用正確的路徑
export PATH="/usr/local/bin:$PATH"

# 設(shè)置環(huán)境變量
export XCODE_PATH=$(xcode-select -p)
export IOS_SDK=$XCODE_PATH/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk
export SIM_SDK=$XCODE_PATH/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk

# 創(chuàng)建輸出目錄
OUTPUT_DIR="build-ios"
rm -rf $OUTPUT_DIR
mkdir -p $OUTPUT_DIR

# 編譯函數(shù)
compile_arch() {
    ARCH=$1
    SDK=$2
    
    BUILD_DIR="${OUTPUT_DIR}/${ARCH}"
    mkdir -p $BUILD_DIR
    pushd $BUILD_DIR > /dev/null
    
    echo "? 配置 $ARCH..."
    cmake ../.. \
        -DCMAKE_SYSTEM_NAME=iOS \
        -DCMAKE_OSX_ARCHITECTURES=$ARCH \
        -DCMAKE_OSX_SYSROOT=$SDK \
        -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \
        -DCMAKE_BUILD_TYPE=Release \
        -DBUILD_SHARED_LIBS=OFF \
        -DENABLE_SM2=ON \
        -DENABLE_SM3=ON \
        -DENABLE_SM4=ON \
        -DENABLE_SM9=ON \
        -G Ninja
        
    echo "? 編譯 $ARCH..."
    ninja
    
    # 關(guān)鍵修改:GmSSL 3.x 生成的庫是 libgmssl.a
    mkdir -p lib
    cp bin/libgmssl.a lib/
    
    popd > /dev/null
}

# 編譯各架構(gòu)
compile_arch "arm64" "$IOS_SDK"
compile_arch "x86_64" "$SIM_SDK"

# 合并通用庫
UNIVERSAL_DIR="${OUTPUT_DIR}/universal"
mkdir -p $UNIVERSAL_DIR/lib

# 合并為單個庫 (GmSSL 3.x 只生成一個庫)
lipo -create \
    "${OUTPUT_DIR}/arm64/lib/libgmssl.a" \
    "${OUTPUT_DIR}/x86_64/lib/libgmssl.a" \
    -output "$UNIVERSAL_DIR/lib/libgmssl.a"

# 復(fù)制頭文件
if [ -d "${OUTPUT_DIR}/arm64/include" ]; then
    cp -R "${OUTPUT_DIR}/arm64/include" "$UNIVERSAL_DIR/"
elif [ -d "../../include" ]; then
    cp -R "../../include" "$UNIVERSAL_DIR/"
else
    echo "?? 警告: 找不到頭文件目錄"
fi

echo "? 編譯成功!"
echo "庫文件位置: $UNIVERSAL_DIR/lib/libgmssl.a"
echo "頭文件位置: $UNIVERSAL_DIR/include"

# 驗(yàn)證文件
file "$UNIVERSAL_DIR/lib"/*.a
lipo -info "$UNIVERSAL_DIR/lib/libgmssl.a"

然后按照以下步驟進(jìn)行執(zhí)行:

# 安裝構(gòu)建工具
brew install cmake ninja pkg-config

# 獲取最新代碼
git clone https://github.com/guanzhi/GmSSL.git
cd GmSSL
git checkout master  # 確保使用最新版本
git pull

# 2. 執(zhí)行編譯
chmod +x build_ios.sh
./build_ios.sh
  1. 將GmSSL/build-ios/universal/lib/libgmssl.a 拖入項(xiàng)目

  2. 將GmSSL/include/gmssl 拖入項(xiàng)目

  3. import "sm3.h"

封裝方法:

@interface GmSSLEncryptorSM3 : NSObject

+ (NSString *)sm3HashWithString:(NSString *)input;
+ (NSData *)sm3HashWithData:(NSData *)data;

@end

@implementation GmSSLEncryptorSM3

+ (instancetype)encryptor {
    return [[GmSSLEncryptorSM3 alloc] init];
}

+ (NSData *)sm3HashWithData:(NSData *)data {
    // 初始化 SM3 上下文
    SM3_CTX ctx;
    sm3_init(&ctx);
    // 添加數(shù)據(jù)到哈希計算
    sm3_update(&ctx, data.bytes, data.length);
    // 準(zhǔn)備存儲結(jié)果的緩沖區(qū) (SM3 輸出為 32 字節(jié))
    uint8_t dgst[SM3_DIGEST_SIZE];
    // 完成哈希計算
    sm3_finish(&ctx, dgst);
    // 轉(zhuǎn)換為 NSData
    return [NSData dataWithBytes:dgst length:SM3_DIGEST_SIZE];
}

+ (NSString *)sm3HashWithString:(NSString *)input {
    NSData *inputData = [input dataUsingEncoding:NSUTF8StringEncoding];
    // 計算 SM3 哈希
    NSData *hashData = [GmSSLEncryptorSM3 sm3HashWithData:inputData];
    // 轉(zhuǎn)換為十六進(jìn)制字符串顯示
    NSMutableString *hexString = [NSMutableString string];
    const uint8_t *bytes = (const uint8_t *)hashData.bytes;
    for (NSUInteger i = 0; i < hashData.length; i++) {
        [hexString appendFormat:@"%02x", bytes[i]];
    }
    return hexString;
}
@end

就可以在項(xiàng)目中使用了:

NSString *encryptor = [GmSSLEncryptorSM3 sm3HashWithString:str];
NSLog(@"%@", encryptor);

方案三:純 Objective-C 實(shí)現(xiàn)(無依賴)(demo)

SM3本質(zhì)上不是加密算法,它是是一種雜湊函數(shù),是在[SHA-256]基礎(chǔ)上改進(jìn)實(shí)現(xiàn)的一種算法,它不是對數(shù)據(jù)進(jìn)行加密然后再解密,而是生成一個256位的散列值,因此SM3適用于內(nèi)容摘要,數(shù)字簽名驗(yàn)證或密碼驗(yàn)證等。

SM3算法的執(zhí)行過程:

根據(jù)SM3標(biāo)準(zhǔn)文檔(GM/T 0004-2012)

sm3流程.png

消息擴(kuò)展:將16個32位字?jǐn)U展為68個字(W)和64個字(W1),使用P1宏。
壓縮函數(shù):64輪迭代更新寄存器(A-H),每輪使用FF1/GG1等宏。
常量:壓縮函數(shù)中的常量0x7A879D8A(TJ的固定值)。
結(jié)果輸出:將最終狀態(tài)寄存器轉(zhuǎn)換為大端序字節(jié)流(256位)。

//
//  SM3Encryptor.m
//  testDemo
//
//  Created by wt on 2025/6/12.
//

#import "SM3Encryptor.h"
#include <stdint.h>

// SM3 上下文結(jié)構(gòu)
typedef struct {
    uint32_t state[8];   // 8個32位寄存器(A-H)
    uint64_t totalLength; // 總消息長度(位)
    uint8_t buffer[64];  // 當(dāng)前數(shù)據(jù)塊緩存
    uint32_t bufferLength; // 當(dāng)前緩沖區(qū)長度
} SM3Context;

// 循環(huán)左移
static inline uint32_t ROTL(uint32_t x, uint8_t n) {
    return (x << n) | (x >> (32 - n));
}

// 布爾函數(shù) FF0(0≤j≤15)
static inline uint32_t FF0(uint32_t x, uint32_t y, uint32_t z) {
    return x ^ y ^ z;
}

// 布爾函數(shù) FF1(16≤j≤63)
static inline uint32_t FF1(uint32_t x, uint32_t y, uint32_t z) {
    return (x & y) | (x & z) | (y & z);
}

// 布爾函數(shù) GG0(0≤j≤15)
static inline uint32_t GG0(uint32_t x, uint32_t y, uint32_t z) {
    return x ^ y ^ z;
}

// 布爾函數(shù) GG1(16≤j≤63)
static inline uint32_t GG1(uint32_t x, uint32_t y, uint32_t z) {
    return (x & y) | ((~x) & z);
}

// 置換函數(shù) P0
static inline uint32_t P0(uint32_t x) {
    return x ^ ROTL(x, 9) ^ ROTL(x, 17);
}

// 置換函數(shù) P1
static inline uint32_t P1(uint32_t x) {
    return x ^ ROTL(x, 15) ^ ROTL(x, 23);
}

// 初始化SM3上下文
void SM3Init(SM3Context *context) {
    // SM3標(biāo)準(zhǔn)初始值
    context->state[0] = 0x7380166F;
    context->state[1] = 0x4914B2B9;
    context->state[2] = 0x172442D7;
    context->state[3] = 0xDA8A0600;
    context->state[4] = 0xA96F30BC;
    context->state[5] = 0x163138AA;
    context->state[6] = 0xE38DEE4D;
    context->state[7] = 0xB0FB0E4E;
    context->totalLength = 0;
    context->bufferLength = 0;
    memset(context->buffer, 0, 64);
}

// 處理單個64字節(jié)塊(壓縮函數(shù)核心)
void SM3Compress(SM3Context *context, const uint8_t block[64]) {
    // 1. 消息擴(kuò)展:16字 → 68字(W) + 64字(W1)
    uint32_t W[68], W1[64];
    
    // 初始化前16字(大端序轉(zhuǎn)換)
    for (int i = 0; i < 16; i++) {
        W[i] = (uint32_t)block[i*4] << 24 |
               (uint32_t)block[i*4+1] << 16 |
               (uint32_t)block[i*4+2] << 8 |
               (uint32_t)block[i*4+3];
    }
    
    // 計算W[16]-W[67]
    for (int j = 16; j < 68; j++) {
        uint32_t temp = W[j-16] ^ W[j-9] ^ ROTL(W[j-3], 15);
        W[j] = P1(temp) ^ ROTL(W[j-13], 7) ^ W[j-6];
    }
    
    // 計算W1[0]-W1[63]
    for (int j = 0; j < 64; j++) {
        W1[j] = W[j] ^ W[j+4];
    }
    
    // 2. 寄存器初始化(A-H)
    uint32_t A = context->state[0];
    uint32_t B = context->state[1];
    uint32_t C = context->state[2];
    uint32_t D = context->state[3];
    uint32_t E = context->state[4];
    uint32_t F = context->state[5];
    uint32_t G = context->state[6];
    uint32_t H = context->state[7];
    
    // 3. 64輪迭代(嚴(yán)格遵循標(biāo)準(zhǔn))
    for (int j = 0; j < 64; j++) {
        uint32_t SS1, SS2, TT1, TT2;
        
        // 常量選擇(關(guān)鍵修正)
        uint32_t TJ = (j < 16) ? 0x79CC4519 : 0x7A879D8A;
        
        // 計算SS1/SS2(修正了TJ參數(shù))
        SS1 = ROTL(ROTL(A, 12) + E + ROTL(TJ, j % 32), 7);
        SS2 = SS1 ^ ROTL(A, 12);
        
        // 計算TT1/TT2(使用內(nèi)聯(lián)函數(shù))
        if (j < 16) {
            TT1 = FF0(A, B, C) + D + SS2 + W1[j];
            TT2 = GG0(E, F, G) + H + SS1 + W[j];
        } else {
            TT1 = FF1(A, B, C) + D + SS2 + W1[j];
            TT2 = GG1(E, F, G) + H + SS1 + W[j];
        }
        
        // 更新寄存器(嚴(yán)格順序)
        D = C;
        C = ROTL(B, 9);
        B = A;
        A = TT1;
        H = G;
        G = ROTL(F, 19);
        F = E;
        E = P0(TT2);
    }
    
    // 4. 更新最終狀態(tài)(與初始IV異或)
    context->state[0] ^= A;
    context->state[1] ^= B;
    context->state[2] ^= C;
    context->state[3] ^= D;
    context->state[4] ^= E;
    context->state[5] ^= F;
    context->state[6] ^= G;
    context->state[7] ^= H;
}

// 更新數(shù)據(jù)(可分多次調(diào)用)
void SM3Update(SM3Context *context, const uint8_t *data, size_t length) {
    context->totalLength += length * 8; // 更新總位數(shù)(字節(jié)轉(zhuǎn)位)
    
    // 處理緩沖區(qū)中的剩余空間
    if (context->bufferLength > 0) {
        size_t copySize = MIN(64 - context->bufferLength, length);
        memcpy(context->buffer + context->bufferLength, data, copySize);
        context->bufferLength += copySize;
        data += copySize;
        length -= copySize;
        
        if (context->bufferLength == 64) {
            SM3Compress(context, context->buffer);
            context->bufferLength = 0;
        }
    }
    
    // 處理完整塊
    while (length >= 64) {
        SM3Compress(context, data);
        data += 64;
        length -= 64;
    }
    
    // 緩存剩余數(shù)據(jù)
    if (length > 0) {
        memcpy(context->buffer, data, length);
        context->bufferLength = length;
    }
}

// 完成哈希計算
void SM3Final(SM3Context *context, uint8_t output[32]) {
    // 計算填充長度(SM3標(biāo)準(zhǔn):補(bǔ)位1 + k個0 + 64位長度)
    size_t totalBits = context->totalLength;
    size_t paddingBits = (context->bufferLength < 56) ?
                         (56 - context->bufferLength) :
                         (120 - context->bufferLength);
    
    // 構(gòu)建填充數(shù)據(jù)
    uint8_t padding[128] = {0};
    padding[0] = 0x80; // 補(bǔ)位起始位(二進(jìn)制10000000)
    
    // 添加填充
    SM3Update(context, padding, paddingBits);
    
    // 添加消息長度(大端序64位)
    uint64_t bitCount = CFSwapInt64HostToBig(totalBits);
    SM3Update(context, (uint8_t *)&bitCount, 8);
    
    // 確保最后一個塊被處理
    if (context->bufferLength > 0) {
        memset(context->buffer + context->bufferLength, 0, 64 - context->bufferLength);
        SM3Compress(context, context->buffer);
    }
    
    // 輸出最終哈希(256位,大端序)
    for (int i = 0; i < 8; i++) {
        output[i*4]     = (uint8_t)(context->state[i] >> 24);
        output[i*4 + 1] = (uint8_t)(context->state[i] >> 16);
        output[i*4 + 2] = (uint8_t)(context->state[i] >> 8);
        output[i*4 + 3] = (uint8_t)(context->state[i]);
    }
}

// Objective-C 封裝接口
@implementation SM3Encryptor

+ (NSData *)hashWithData:(NSData *)inputData {
    SM3Context context;
    SM3Init(&context);
    
    // 處理輸入數(shù)據(jù)
    SM3Update(&context, inputData.bytes, inputData.length);
    
    // 獲取結(jié)果
    uint8_t output[32];
    SM3Final(&context, output);
    
    return [NSData dataWithBytes:output length:32];
}

+ (NSString *)hexStringWithData:(NSData *)inputData {
    NSData *hashData = [self hashWithData:inputData];
    const uint8_t *bytes = (const uint8_t *)hashData.bytes;
    NSMutableString *hex = [NSMutableString string];
    
    for (NSUInteger i = 0; i < hashData.length; i++) {
        [hex appendFormat:@"%02X", bytes[i]];
    }
    
    return [hex copy];
}

+ (NSString *)hexStringWithInput:(NSString *)inputStr {
    NSData *inputData = [inputStr dataUsingEncoding:NSUTF8StringEncoding];
    NSData *hashData = [self hashWithData:inputData];
    const uint8_t *bytes = (const uint8_t *)hashData.bytes;
    NSMutableString *hex = [NSMutableString string];
    
    for (NSUInteger i = 0; i < hashData.length; i++) {
        [hex appendFormat:@"%02X", bytes[i]];
    }
    
    return [hex copy];
}


@end

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容