引言

在万物互联时代,数据安全已成为操作系统的核心战场。鸿蒙系统(HarmonyOS)凭借其创新的文件指纹加密分级文件加密机制,构建了从代码到数据的全生命周期防护体系。本文将从技术原理、实现方案、应用场景三个维度深度解析这两大安全特性。

原理参考:

使用@ohos.security.huks密钥管理服务与@ohos.userIAM.userAuth用户认证服务实现指纹识别后加解密的功能。

实现步骤:
  • 初始化密钥会话获取挑战值与handle。

  • 发起指纹识别获取token。

  • 完成密钥会话实现加解密。

所需权限:

  • 需要申请权限:ohos.permission.ACCESS_BIOMETRIC。

  • 需要录入设备指纹。

代码实现与步骤:

首先需要在module.json5中配置指纹权限

"requestPermissions": [
      {
        "name": "ohos.permission.ACCESS_BIOMETRIC",
        "reason": "$string:access_biometric_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      ],

再建立一个方法专门生成密钥,代码参考官方文档文档中心

需要有一个密钥别名和初始化密钥的属性参数集

/*
* 确定密钥别名和封装密钥属性参数集
*/
let keyAlias = 'test_sm4_key_alias';

class ThrowObject {
  isThrow: boolean = false
}

let properties: Array<huks.HuksParam> = [{
  tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
  value: huks.HuksKeyAlg.HUKS_ALG_SM4
}, {
  tag: huks.HuksTag.HUKS_TAG_PURPOSE,
  value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT
}, {
  tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
  value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128,
}, {
  tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
  value: huks.HuksCipherMode.HUKS_MODE_CBC,
}, {
  tag: huks.HuksTag.HUKS_TAG_PADDING,
  value: huks.HuksKeyPadding.HUKS_PADDING_PKCS7,
},
  // 指定密钥身份认证的类型:指纹
  {
    tag: huks.HuksTag.HUKS_TAG_USER_AUTH_TYPE,
    value: huks.HuksUserAuthType.HUKS_USER_AUTH_TYPE_FINGERPRINT
  },
  // 指定密钥安全授权的类型(失效类型):新录入生物特征(指纹)后无效
  {
    tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_ACCESS_TYPE,
    value: huks.HuksAuthAccessType.HUKS_AUTH_ACCESS_INVALID_NEW_BIO_ENROLL
  },
  // 指定挑战值的类型:默认类型
  {
    tag: huks.HuksTag.HUKS_TAG_CHALLENGE_TYPE,
    value: huks.HuksChallengeType.HUKS_CHALLENGE_TYPE_NORMAL
  },
  {
    tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_PURPOSE,
    value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT
  }];

let huksOptions: huks.HuksOptions = {
  properties: properties,
  inData: new Uint8Array(new Array())
}

生成密钥的方法:使用 SM4 算法对数据进行加密的功能,并且在加密过程中加入了指纹认证环节,以此确保数据的安全性。

  1. 参数设置:明确密钥别名、初始化向量(IV)、认证类型等参数,同时配置加密所需的属性参数集。
  2. 加密准备:调用 encrypt 函数,把明文转换为字节数组,接着初始化加密会话以获取挑战值和会话句柄。
  3. 身份认证:调用 userIAMAuthFingerEncrypt 函数,借助指纹认证工具包拉起指纹认证界面。
  4. 认证处理:订阅认证结果,若认证成功,获取认证令牌,然后调用 publicEncryptFinishFunc 函数完成加密操作。
  5. 加密完成:加密结束后,可通过 getCipherText 函数获取加密后的密文数据。

注意事项

  • 此代码依赖 @kit.UniversalKeystoreKit 和 @kit.UserAuthenticationKit 工具包,使用前需确保这些工具包已正确安装与配置。
  • 代码中假定非分段加密,所以未调用 publicEncryptUpdateFunc 函数。若为分段加密,需取消该函数的注释并调用。
function generateKeyItem(keyAlias: string, huksOptions: huks.HuksOptions, throwObject: ThrowObject) {
  return new Promise<void>((resolve, reject) => {
    try {
      huks.generateKeyItem(keyAlias, huksOptions, (error, data) => {
        if (error) {
          promptAction.showToast({
            message: '密钥生成失败,错误码是: ' + error.code + ' 错误吗信息: ' + error.message,
            duration: 6500,
          })
          reject(error);
        } else {
          console.info(`成功生成了一个别名为:${keyAlias}的密钥`)
          resolve(data);
        }
      });
    } catch (error) {
      throwObject.isThrow = true;
      throw (error as Error);
    }
  });
}

async function publicGenKeyFunc(keyAlias: string, huksOptions: huks.HuksOptions) {
  console.info(`enter promise generateKeyItem`);
  let throwObject: ThrowObject = { isThrow: false };
  try {
    await generateKeyItem(keyAlias, huksOptions, throwObject)
      .then((data) => {
        console.info(`promise: generateKeyItem success, data = ${JSON.stringify(data)}`);
      })
      .catch((error: Error) => {
        if (throwObject.isThrow) {
          throw (error as Error);
        } else {
          console.error(`promise: generateKeyItem failed, ` + JSON.stringify(error));
        }
      });
  } catch (error) {
    console.error(`promise: generateKeyItem input arg invalid, ` + JSON.stringify(error));
  }
}

// 生成密钥
export async function TestGenKeyForFingerprintAccessControl() {
  await publicGenKeyFunc(keyAlias, huksOptions);
}

那我们既然生成了密钥,将来读取这个文件的时候也是需要解密,所以我们还要编写解密的方法。

  1. 参数设置

    • 设定密钥别名 keyAlias、初始化向量 IV 以及认证类型 authType(这里是指纹认证)。
    • 定义解密所需的属性参数集 propertiesDecrypt,包含算法、用途、密钥大小、填充模式、块模式、初始化向量等信息。
    • 初始化解密选项 decryptOptions,将解密属性参数和输入数据(初始为空)传入。
  2. 解密准备

    • 调用 decrypt 函数,传入密文 cipherText
    • 将密文赋值给 decryptOptions.inData
    • 调用 publicInitDecryptFunc 函数初始化解密会话,获取挑战值 challenge 和会话句柄 handle
  3. 身份认证

    • 调用 userIAMAuthFingerDecrypt 函数,传入挑战值 challenge
    • 配置认证参数 authParam 和界面参数 widgetParam
    • 获取认证实例 auth,若获取失败则输出错误信息并返回。
    • 订阅认证结果事件,若认证成功,获取认证令牌 fingerAuthToken,并调用 publicDecryptFinishFunc 函数完成解密操作。
  4. 解密完成

    • 在 publicDecryptFinishFunc 函数中,调用 finishDecryptSession 函数完成解密会话,获取解密后的明文 plainTextOutput
    • 可以通过 getPlainTextOutput 函数将解密后的字节数组转换为字符串并返回。
/*
 * 解密
 * */
// 引入通用密钥库工具包
import { huks } from '@kit.UniversalKeystoreKit';
// 引入类型转换工具
import { StringToUint8Array, TypeUtil } from './TypeUtil';
// 引入基础服务工具包中的业务错误类
import { BusinessError } from '@kit.BasicServicesKit';
// 引入用户认证工具包
import { userAuth } from '@kit.UserAuthenticationKit';

/*
* 确定封装密钥属性参数集
*/
// 密钥别名
let keyAlias = 'test_sm4_key_alias';
// 初始化向量
let IV = '1234567890123456';
// 会话句柄,初始化为 0
let handle = 0;
// 挑战值
let challenge: Uint8Array;
// 指纹认证令牌
let fingerAuthToken: Uint8Array;
// 解密后的明文数据
let plainTextOutput: Uint8Array; 
// 认证类型为指纹认证
let authType = userAuth.UserAuthType.FINGERPRINT;

// 抛出异常对象
class ThrowObject {
    // 是否抛出异常的标志
    isThrow: boolean = false;
}

/* 集成生成密钥参数集 & 解密参数集 */
class PropertyDecryptType {
    // 密钥标签
    tag: huks.HuksTag = huks.HuksTag.HUKS_TAG_ALGORITHM;
    // 密钥值,类型可以是算法、用途、大小、填充模式、加密模式或字节数组
    value: huks.HuksKeyAlg | huks.HuksKeyPurpose | huks.HuksKeySize | huks.HuksKeyPadding | huks.HuksCipherMode
        | Uint8Array = huks.HuksKeyAlg.HUKS_ALG_SM4;
}

// 解密属性参数数组
let propertiesDecrypt: PropertyDecryptType[] = [
    {
        // 算法标签
        tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
        // 算法为 SM4
        value: huks.HuksKeyAlg.HUKS_ALG_SM4,
    },
    {
        // 用途标签
        tag: huks.HuksTag.HUKS_TAG_PURPOSE,
        // 用途为解密
        value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT,
    },
    {
        // 密钥大小标签
        tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
        // 密钥大小为 128 位
        value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128,
    },
    {
        // 填充模式标签
        tag: huks.HuksTag.HUKS_TAG_PADDING,
        // 填充模式为 PKCS7
        value: huks.HuksKeyPadding.HUKS_PADDING_PKCS7,
    },
    {
        // 块模式标签
        tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
        // 加密模式为 CBC
        value: huks.HuksCipherMode.HUKS_MODE_CBC,
    },
    {
        // 初始化向量标签
        tag: huks.HuksTag.HUKS_TAG_IV,
        // 将初始化向量转换为字节数组
        value: StringToUint8Array(IV),
    },
    {
        // 密钥认证用途标签
        tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_PURPOSE,
        // 密钥认证用途为解密
        value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT
    }
]

// 解密选项
let decryptOptions: huks.HuksOptions = {
    // 解密属性参数
    properties: propertiesDecrypt,
    // 输入数据
    inData: new Uint8Array(new Array())
}

// 解密函数
export async function decrypt(cipherText: Uint8Array) {
    /* 认证成功后进行解密, 需要传入Auth获取到的authToken值 */
    // 将密文作为输入数据
    decryptOptions.inData = cipherText;
    /* 初始化密钥会话获取挑战值与handle */
    // 初始化解密会话
    await publicInitDecryptFunc(keyAlias, decryptOptions);
    /* 调用userIAM进行身份认证 */
    // 调用指纹认证
    userIAMAuthFingerDecrypt(challenge);
}

/* 初始化HUKS中的会话,获取挑战值与handle */
async function publicInitDecryptFunc(keyAlias: string, huksOptions: huks.HuksOptions) {
    console.info(`进入初始化会话函数`);
    let throwObject: ThrowObject = { isThrow: false };
    try {
        await initDecryptSession(keyAlias, huksOptions, throwObject)
           .then((data) => {
                console.info(`初始化会话成功, 数据 = ${JSON.stringify(data)}`);
                // 获取会话句柄
                handle = data.handle;
                // 获取挑战值
                challenge = data.challenge as Uint8Array;
            })
           .catch((error: BusinessError) => {
                if (throwObject.isThrow) {
                    throw (error as Error);
                } else {
                    console.error(`初始化会话失败` + JSON.stringify(error));
                }
            });
    } catch (error) {
        console.error(`初始化会话输入参数无效` + JSON.stringify(error));
    }
}

// 初始化解密会话函数
function initDecryptSession(keyAlias: string, huksOptions: huks.HuksOptions, throwObject: ThrowObject) {
    return new Promise<huks.HuksSessionHandle>((resolve, reject) => {
        try {
            huks.initSession(keyAlias, huksOptions, (error, data) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(data);
                }
            });
        } catch (error) {
            throwObject.isThrow = true;
            throw (error as Error);
        }
    });
}

// 完成解密会话函数
export async function publicDecryptFinishFunc(handle: number, token: Uint8Array, huksOptions: huks.HuksOptions) {
    console.info(`进入完成会话函数`);
    let throwObject: ThrowObject = { isThrow: false };
    try {
        await finishDecryptSession(handle, huksOptions, token, throwObject)
           .then((data) => {
                // 获取解密后的明文
                plainTextOutput = data.outData as Uint8Array;
                console.info(`完成会话成功, 数据 = ${JSON.stringify(data)}`);
                console.info(`完成会话成功, 原始数据 = ${JSON.stringify(data.outData)}`);
            })
           .catch((error: BusinessError) => {
                if (throwObject.isThrow) {
                    throw (error as Error);
                } else {
                    console.error(`完成会话失败` + JSON.stringify(error));
                }
            });
    } catch (error) {
        console.error(`完成会话输入参数无效` + JSON.stringify(error));
    }
}

// 完成解密会话函数
function finishDecryptSession(handle: number, huksOptions: huks.HuksOptions, token: Uint8Array,
    throwObject: ThrowObject) {
    return new Promise<huks.HuksReturnResult>((resolve, reject) => {
        try {
            huks.finishSession(handle, huksOptions, token, (error, data) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(data);
                }
            });
        } catch (error) {
            throwObject.isThrow = true;
            throw (error as Error);
        }
    });
}

// 更新解密会话函数
async function publicDecryptUpdateFunc(handle: number, token: Uint8Array, huksOptions: huks.HuksOptions) {
    console.info(`进入更新会话函数`);
    let throwObject: ThrowObject = { isThrow: false };
    try {
        await updateSession(handle, huksOptions, token, throwObject)
           .then((data) => {
                console.info(`更新会话成功, 数据 = ${JSON.stringify(data)}`);
            })
           .catch((error: Error) => {
                if (throwObject.isThrow) {
                    throw (error as Error);
                } else {
                    console.error(`更新会话失败, ` + JSON.stringify(error));
                }
            });
    } catch (error) {
        console.error(`更新会话输入参数无效, ` + JSON.stringify(error));
    }
}

// 更新会话函数
function updateSession(handle: number, huksOptions: huks.HuksOptions, token: Uint8Array, throwObject: ThrowObject) {
    return new Promise<huks.HuksReturnResult>((resolve, reject) => {
        try {
            huks.updateSession(handle, huksOptions, token, (error, data) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(data);
                }
            });
        } catch (error) {
            throwObject.isThrow = true;
            throw (error as Error);
        }
    });
}

/* 调用UserIAM拉起指纹认证,触发HUKS的访问控制流程 */
export function userIAMAuthFingerDecrypt(huksChallenge: Uint8Array) {
    // 获取认证对象
    let authTypeList: userAuth.UserAuthType[] = [authType];
    const authParam: userAuth.AuthParam = {
        // 挑战值
        challenge: huksChallenge,
        // 认证类型
        authType: authTypeList,
        // 认证信任级别
        authTrustLevel: userAuth.AuthTrustLevel.ATL1
    };
    const widgetParam: userAuth.WidgetParam = {
        // 认证提示标题
        title: '请验证身份进行解密',
    };
    let auth: userAuth.UserAuthInstance;
    try {
        // 获取认证实例
        auth = userAuth.getUserAuthInstance(authParam, widgetParam);
        console.info('获取认证实例成功');
    } catch (error) {
        console.error('获取认证实例失败' + JSON.stringify(error));
        return;
    }
    // 订阅认证结果
    try {
        auth.on('result', {
            onResult(result) {
                // 认证成功
                console.info('[HUKS] -> [IAM]  用户认证实例回调结果 = ' + JSON.stringify(result));
                // 获取指纹认证令牌
                fingerAuthToken = result.token;
                /*
                 * 传入认证令牌进行解密
                 * 非分段加密不需要执行publicDecryptUpdateFunc方法
                 * publicDecryptUpdateFunc(handle, result.token, decryptOptions);
                 * */
                // 完成解密会话
                publicDecryptFinishFunc(handle, result.token, decryptOptions);
            }
        });
        console.log('订阅认证事件成功');
    } catch (error) {
        console.error('订阅认证事件失败, ' + JSON.stringify(error));
    }
    // 开始认证
    try {
        auth.start();
        console.info('开始指纹认证成功');
    } catch (error) {
        console.error('开始指纹认证失败, 错误 = ' + JSON.stringify(error));
    }
}

// 获取明文数据方法
export function getPlainTextOutput(): string {
    return TypeUtil.arrayToString(plainTextOutput);
}

数据类型转换类

import { util } from '@kit.ArkTS';

export class TypeUtil {

  // 字符串转为Uint8Array
  static stringToArray(str: string) : Uint8Array {
    let textEncoder = new util.TextEncoder('utf-8');
    return textEncoder.encodeInto(str);
  }

  //Uint8Array转为字符串
  static arrayToString(arr: Uint8Array): string {
    let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
    let str = textDecoder.decodeToString(arr, { stream: false })
    return str;
  }
}
// 字符串转为Uint8Array
// 遍历字符串的每个字符,获取其Unicode编码值并存储到数组中,最后转换为Uint8Array
export function StringToUint8Array(str: string) {
  let arr: number[] = [];
  for (let i = 0, j = str.length; i < j; ++i) {
    arr.push(str.charCodeAt(i));
  }
  return new Uint8Array(arr);
}
// 字符串转为Uint8Array
// 遍历字符串的每个字符,获取其Unicode编码值并存储到数组中,最后转换为Uint8Array
export function Uint8ArrayToString(fileData: Uint8Array) {
  let dataString = '';
  for (let i = 0; i < fileData.length; i++) {
    dataString += String.fromCharCode(fileData[i]);
  }
  return dataString;
}

接着我们需要在UIability里的onCreate里创建分布式数据库,创建了 KVManager 实例,接着通过 KVManager 获取 KVStore 实例,最后对数据库进行数据的存储和读取操作。这样就能保证在应用程序启动时数据库就已准备好,为后续操作提供支持。

/*
     * 分布式键值数据库:
     * Select database is exists,
     * Result type is true,
     * Query dataset verification,
     * */
    let context = this.context;
    const kvManagerConfig: distributedKVStore.KVManagerConfig = {
      context: context,
      bundleName: this.context.abilityInfo.bundleName,
    };
    try {
      // 创建KVManager实例
      kvManager = distributedKVStore.createKVManager(kvManagerConfig) as distributedKVStore.KVManager;
      console.info('Succeeded in creating KVManager.');
    } catch (e) {
      let error = e as BusinessError;
      console.error(`Failed to create KVManager. Code:${error.code},message:${error.message}`);
    }
    if (kvManager !== undefined) {
      kvManager = kvManager as distributedKVStore.KVManager;
      // 进行后续创建数据库等相关操作
      try {
        const options: distributedKVStore.Options = {
          createIfMissing: true,
          encrypt: false,
          backup: false,
          autoSync: false,
          // kvStoreType不填时,默认创建多设备协同数据库
          kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
          // 多设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
          securityLevel: distributedKVStore.SecurityLevel.S3
        };
        kvManager.getKVStore<distributedKVStore.SingleKVStore>(this.storeId, options, (err, store: distributedKVStore.SingleKVStore) => {
          if (err) {
            console.error(`Failed to get KVStore: Code:${err.code},message:${err.message}`);
            return;
          }
          console.info('Succeeded in getting KVStore.');
          kvStore = store as distributedKVStore.SingleKVStore;
          // 请确保获取到键值数据库实例后,再进行相关数据操作
          if (kvStore !== undefined) {
            kvStore = kvStore as distributedKVStore.SingleKVStore;
            //进行后续操作
            try {
              const KEY_TEST_STRING_ELEMENT = 'key_test_string';
              const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
              kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
                if (err !== undefined) {
                  console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
                  return;
                }
                console.info('Succeeded in putting data.');
              });
              kvStore.get(KEY_TEST_STRING_ELEMENT, (err, data) => {
                if (err !== undefined) {
                  console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
                  return;
                }
                console.info(`Succeeded in getting data. Data:${data}`);
              });
            } catch (e) {
              let error = e as BusinessError;
              console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
            }
          }
        });
      } catch (e) {
        let error = e as BusinessError;
        console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
      }
    }

页面调用加密

.onClick(async () => {
            // 加密时明文不能为空
            if (this.plainTextInput === null || this.plainTextInput === '') {
              showAlertDialog('请输入需要加密的日记')
            } else {
              // 加密
              encrypt(this.plainTextInput).then(() => {
                console.log('加密成功');
              }).catch((err: BusinessError) => {
                console.error('encrypt promise failed, because: ' + err.code);
              });
              setTimeout(() => {
                console.log('等待加载');
                // 密文存入数据库
                let cipherText = getCipherText()
                if (kvStore !== undefined) {
                  //kvStore = kvStore as distributedKVStore.SingleKVStore;
                  //进行后续操作
                  try {
                    let KEY_TEST_STRING_ELEMENT = this.days;
                    let VALUE_TEST_STRING_ELEMENT = Uint8ArrayToString(cipherText);
                    console.info('value size:' + VALUE_TEST_STRING_ELEMENT.length)
                    kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
                      if (err !== undefined) {
                        console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
                        return;
                      }
                      console.info('Succeeded in putting data.');
                    });
                  } catch (e) {
                    let error = e as BusinessError;
                    console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
                  }
                }
              },3000)
            }
          })

调用解密:

.onClick(async () => {
            if (kvStore !== undefined) {
              // kvStore = kvStore as distributedKVStore.SingleKVStore;
              //进行后续操作
              try {
                let KEY_TEST_STRING_ELEMENT = this.days;
                kvStore.get(KEY_TEST_STRING_ELEMENT, async (err, data) => {
                  if (err !== undefined) {
                    console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
                    return;
                  }
                  console.info(`Succeeded in getting data. Data:${data}`);
                  let cipherText = StringToUint8Array(data as string);
                  await decrypt(cipherText as Uint8Array);
                  // 延迟1.5秒获取明文,等待密文解密成功
                  setTimeout(() => {
                    console.log('输出结果');
                    this.plainTextOutput = getPlainTextOutput()
                    console.log('result:' + this.plainTextOutput)
                  },3000)
                });
              } catch (e) {
                let error = e as BusinessError;
                console.error(`An unexpected error occurred. Code:${error.code},message:${error.message}`);
              }
            }
          })

适用HarmonyOS NEXT / API12或以上版本 -----------------

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐