近期的一個項目需要用到SM3加密算法,需要在iOS中使用Objective-C實現SM3國密加密算法。
SM3:是中國國家密碼管理局發布的密碼雜湊算法標準,適用于商用密碼應用中的數字簽名和驗證、消息認證碼的生成與驗證以及隨機數的生成等
由于iOS系統并未內置SM3算法,我們需要使用第三方開源庫或自己實現
GMObjC庫: 是一個基于 OpenSSL 的國密(SM2、SM3、SM4)算法的 Objective-C 開源庫,適用于 iOS 和 macOS 開發。它封裝了中國國家密碼管理局發布的多種加密算法,包括:
1. SM2: 支持基于橢圓曲線(ECC)的加解密,密鑰協商(ECDH)和簽名算法
2. SM3: 類似 SHA 系列的國密哈希算法,包含 SM3 和 HMAC 等
3. SM4: 實現對稱分組加密算法
**GmSSL庫:**GmSSL是由北京大學自主開發的國產商用密碼開源庫,實現了對國密算法、標準和安全通信協議的全面功能覆蓋,支持包括移動端在內的主流操作系統和處理器,支持密碼鑰匙、密碼卡等典型國產密碼硬件,提供功能豐富的命令行工具及多種編譯語言編程接口
方案一:使用第三方庫(GMObjC)
集成GMObjC:集成GMObjC方法
因為我們的項目是SDK不便用CocoaPods方法,因此我只能選擇直接集成和手動編譯為 Framework。
1.直接集成 (demo)
1.從 Git 下載最新代碼,找到和 README 同級的 GMObjC 文件夾,將 GMObjC 文件夾拖入項目
2.找到和 README 同級的 Frameworks 文件夾,將項目 Frameworks/OpenSSL.xcframework 拖入項目
3.在需要使用的地方導入頭文件 GMObjC.h 即可使用 SM2、SM4 加解密,簽名驗簽,計算 SM3 摘要等
注意事項
GMObjC 依賴 OpenSSL,可直接拖入 Frameworks/OpenSSL.xcframework 或通過pod GMOpenSSL安裝 OpenSSL。
如果項目中已集成 OpenSSL 1.1.1l 以上版本,可共用同一個 OpenSSL;否則需要使用 Carthage 將 GMObjC 編譯為動態庫。
我按照以上步驟將文件導入后報錯:
OpenSSL.xcframework 簽名驗證失敗
終端執行強制重簽名命令
codesign --force --deep --sign - 你的路徑/OpenSSL.xcframework
返回:你的路徑/OpenSSL.xcframework: replacing existing signature
現在就可以運行測試了:
#import "GMObjC.h"NSString *str = @"123@1234";
NSString *digest = [GMSm3Utils hashWithText:str];
NSLog(@"%@", digest);
2.手動編譯為 Framework (demo)
1.動態庫?:
從 GitHub 下載源碼,打開項目GMObjC-master/Frameworks/GMObjC.xcframework把這個拖入項目
在 Xcode 的 General → Frameworks, Libraries, and Embedded Content 中需標記為 Embed & Sign
#import "GMObjC/GMObjC.h"NSString *digest1 = [GMSm3Utils hashWithText:str];
NSLog(@"%@", digest1);
2.靜態庫:
從 GitHub 下載源碼,打開項目 GMObjC.xcodeproj,設置 Build Settings - Linking-General - Mach-O Type 為 Static Library
手動編譯為靜態庫 GMObjC.framework
**合并為 XCFramework:**通過xcodebuild -create-xcframework命令來合并為 XCFramework,通過合并 GMObjC 庫的模擬器和真機版本來演示
# 創建合并包 GMObjC.xcframeworkxcodebuild -create-xcframework \-framework Release-iphoneos/GMObjC.framework \-framework Release-iphonesimulator/GMObjC.framework \-output GMObjC.xcframework
把生成的GMObjC.xcframework拖入項目即可
3.CocoaPods安裝GMObjC (GMObjC-demo) (GMDynamic-demo)
GMObjC 和 GMDynamic 只能安裝其中一個,二者不能同時安裝。
GMObjC 為靜態庫,GMDynamic 為編譯好的 GMObjC 動態庫版本。
# 安裝 GMObjC 的源碼和 GMOpenSSL.xcframework (靜態庫)
pod 'GMObjC', '~> 4.0.3'# 當 Podfile 中使用 use_frameworks! 時,安裝 GMObjC.xcframework (動態庫)
pod 'GMDynamic', '~> 4.0.3'
方案二:使用第三方庫(GmSSL)(demo)
集成GmSSL:
但是我用這種方法不行,我用了其他的方法。
我們使用GmSSL 3.x(master分支)來編譯iOS的靜態庫(libcrypto.a和libssl.a)。由于3.x版本采用了CMake構建系統,因此流程與2.x不同。
GmSSL 3.x 的構建系統已經發生了變化,生成的庫文件名為 libgmssl.a 而不是傳統的 libcrypto.a 和 libssl.a。
如果項目必須使用
libcrypto.a
和libssl.a
,請回退到 GmSSL 2.x
-
克隆代碼并切換到master分支(或最新的穩定標簽)
-
配置CMake工具鏈文件(為iOS交叉編譯)
-
分別編譯arm64(真機)和x86_64(模擬器)架構
-
使用lipo合并成通用靜態庫
-
將生成的靜態庫和頭文件集成到iOS項目中。
創建編譯腳本: build_ios.sh(放在GmSSL根目錄)
#!/bin/bash
set -e# 確保使用正確的路徑
export PATH="/usr/local/bin:$PATH"# 設置環境變量
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# 創建輸出目錄
OUTPUT_DIR="build-ios"
rm -rf $OUTPUT_DIR
mkdir -p $OUTPUT_DIR# 編譯函數
compile_arch() {ARCH=$1SDK=$2BUILD_DIR="${OUTPUT_DIR}/${ARCH}"mkdir -p $BUILD_DIRpushd $BUILD_DIR > /dev/nullecho "? 配置 $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 Ninjaecho "? 編譯 $ARCH..."ninja# 關鍵修改:GmSSL 3.x 生成的庫是 libgmssl.amkdir -p libcp bin/libgmssl.a lib/popd > /dev/null
}# 編譯各架構
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"# 復制頭文件
if [ -d "${OUTPUT_DIR}/arm64/include" ]; thencp -R "${OUTPUT_DIR}/arm64/include" "$UNIVERSAL_DIR/"
elif [ -d "../../include" ]; thencp -R "../../include" "$UNIVERSAL_DIR/"
elseecho "?? 警告: 找不到頭文件目錄"
fiecho "? 編譯成功!"
echo "庫文件位置: $UNIVERSAL_DIR/lib/libgmssl.a"
echo "頭文件位置: $UNIVERSAL_DIR/include"# 驗證文件
file "$UNIVERSAL_DIR/lib"/*.a
lipo -info "$UNIVERSAL_DIR/lib/libgmssl.a"
然后按照以下步驟進行執行:
# 安裝構建工具
brew install cmake ninja pkg-config# 獲取最新代碼
git clone https://github.com/guanzhi/GmSSL.git
cd GmSSL
git checkout master # 確保使用最新版本
git pull# 2. 執行編譯
chmod +x build_ios.sh
./build_ios.sh
-
將GmSSL/build-ios/universal/lib/libgmssl.a 拖入項目
-
將GmSSL/include/gmssl 拖入項目
-
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);// 添加數據到哈希計算sm3_update(&ctx, data.bytes, data.length);// 準備存儲結果的緩沖區 (SM3 輸出為 32 字節)uint8_t dgst[SM3_DIGEST_SIZE];// 完成哈希計算sm3_finish(&ctx, dgst);// 轉換為 NSDatareturn [NSData dataWithBytes:dgst length:SM3_DIGEST_SIZE];
}+ (NSString *)sm3HashWithString:(NSString *)input {NSData *inputData = [input dataUsingEncoding:NSUTF8StringEncoding];// 計算 SM3 哈希NSData *hashData = [GmSSLEncryptorSM3 sm3HashWithData:inputData];// 轉換為十六進制字符串顯示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
就可以在項目中使用了:
NSString *encryptor = [GmSSLEncryptorSM3 sm3HashWithString:str];
NSLog(@"%@", encryptor);
方案三:純 Objective-C 實現(無依賴)(demo)
SM3本質上不是加密算法,它是是一種雜湊函數,是在[SHA-256]基礎上改進實現的一種算法,它不是對數據進行加密然后再解密,而是生成一個256位的散列值,因此SM3適用于內容摘要,數字簽名驗證或密碼驗證等。
SM3算法的執行過程:
根據SM3標準文檔(GM/T 0004-2012)
**消息擴展:**將16個32位字擴展為68個字(W)和64個字(W1),使用P1宏。
**壓縮函數:**64輪迭代更新寄存器(A-H),每輪使用FF1/GG1等宏。
**常量:**壓縮函數中的常量0x7A879D8A(TJ的固定值)。
**結果輸出:**將最終狀態寄存器轉換為大端序字節流(256位)。
//
// SM3Encryptor.m
// testDemo
//
// Created by wt on 2025/6/12.
//#import "SM3Encryptor.h"
#include <stdint.h>// SM3 上下文結構
typedef struct {uint32_t state[8]; // 8個32位寄存器(A-H)uint64_t totalLength; // 總消息長度(位)uint8_t buffer[64]; // 當前數據塊緩存uint32_t bufferLength; // 當前緩沖區長度
} SM3Context;// 循環左移
static inline uint32_t ROTL(uint32_t x, uint8_t n) {return (x << n) | (x >> (32 - n));
}// 布爾函數 FF0(0≤j≤15)
static inline uint32_t FF0(uint32_t x, uint32_t y, uint32_t z) {return x ^ y ^ z;
}// 布爾函數 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);
}// 布爾函數 GG0(0≤j≤15)
static inline uint32_t GG0(uint32_t x, uint32_t y, uint32_t z) {return x ^ y ^ z;
}// 布爾函數 GG1(16≤j≤63)
static inline uint32_t GG1(uint32_t x, uint32_t y, uint32_t z) {return (x & y) | ((~x) & z);
}// 置換函數 P0
static inline uint32_t P0(uint32_t x) {return x ^ ROTL(x, 9) ^ ROTL(x, 17);
}// 置換函數 P1
static inline uint32_t P1(uint32_t x) {return x ^ ROTL(x, 15) ^ ROTL(x, 23);
}// 初始化SM3上下文
void SM3Init(SM3Context *context) {// SM3標準初始值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字節塊(壓縮函數核心)
void SM3Compress(SM3Context *context, const uint8_t block[64]) {// 1. 消息擴展:16字 → 68字(W) + 64字(W1)uint32_t W[68], W1[64];// 初始化前16字(大端序轉換)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輪迭代(嚴格遵循標準)for (int j = 0; j < 64; j++) {uint32_t SS1, SS2, TT1, TT2;// 常量選擇(關鍵修正)uint32_t TJ = (j < 16) ? 0x79CC4519 : 0x7A879D8A;// 計算SS1/SS2(修正了TJ參數)SS1 = ROTL(ROTL(A, 12) + E + ROTL(TJ, j % 32), 7);SS2 = SS1 ^ ROTL(A, 12);// 計算TT1/TT2(使用內聯函數)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];}// 更新寄存器(嚴格順序)D = C;C = ROTL(B, 9);B = A;A = TT1;H = G;G = ROTL(F, 19);F = E;E = P0(TT2);}// 4. 更新最終狀態(與初始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;
}// 更新數據(可分多次調用)
void SM3Update(SM3Context *context, const uint8_t *data, size_t length) {context->totalLength += length * 8; // 更新總位數(字節轉位)// 處理緩沖區中的剩余空間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;}// 緩存剩余數據if (length > 0) {memcpy(context->buffer, data, length);context->bufferLength = length;}
}// 完成哈希計算
void SM3Final(SM3Context *context, uint8_t output[32]) {// 計算填充長度(SM3標準:補位1 + k個0 + 64位長度)size_t totalBits = context->totalLength;size_t paddingBits = (context->bufferLength < 56) ?(56 - context->bufferLength) :(120 - context->bufferLength);// 構建填充數據uint8_t padding[128] = {0};padding[0] = 0x80; // 補位起始位(二進制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);// 處理輸入數據SM3Update(&context, inputData.bytes, inputData.length);// 獲取結果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