0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

HarmonyOS開發(fā)案例:【字符串加解密】

jf_46214456 ? 來源:jf_46214456 ? 作者:jf_46214456 ? 2024-04-21 21:36 ? 次閱讀

介紹

本Codelab針對用戶隱私安全,使用加密算法API對密碼進行加密存儲,模擬開發(fā)一個用戶注冊登錄應用。實現(xiàn)如下功能:

  1. 實現(xiàn)登錄、注冊、登錄成功頁面。
  2. 注冊的用戶數(shù)據(jù)保存到關(guān)系型數(shù)據(jù)庫。
  3. 登錄時通過查詢數(shù)據(jù)庫校驗用戶是否存在、密碼是否正確。
  4. 密碼通過加密算法保存和使用。

相關(guān)概念

  • [加解密算法庫框架]:為屏蔽底層硬件和算法庫,向上提供統(tǒng)一的密碼算法庫加解密相關(guān)接口。
  • [關(guān)系型數(shù)據(jù)庫]:關(guān)系型數(shù)據(jù)庫(Relational Database,RDB)是一種基于關(guān)系模型來管理數(shù)據(jù)的數(shù)據(jù)庫。

環(huán)境搭建

軟件要求

  • [DevEco Studio]版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 開發(fā)板類型:[潤和RK3568開發(fā)板]。
  • OpenHarmony系統(tǒng):3.2 Release。

環(huán)境搭建

完成本篇Codelab我們首先要完成開發(fā)環(huán)境的搭建,本示例以RK3568開發(fā)板為例,參照以下步驟進行:

  1. [獲取OpenHarmony系統(tǒng)版本]:標準系統(tǒng)解決方案(二進制)。以3.2 Release版本為例:
  2. 搭建燒錄環(huán)境。
    1. [完成DevEco Device Tool的安裝]
    2. [完成RK3568開發(fā)板的燒錄]
  3. 搭建開發(fā)環(huán)境。
    1. 開始前請參考[工具準備],完成DevEco Studio的安裝和開發(fā)環(huán)境配置。
    2. 開發(fā)環(huán)境配置完成后,請參考[使用工程向?qū)創(chuàng)建工程(模板選擇“Empty Ability”)。
    3. 工程創(chuàng)建完成后,選擇使用[真機進行調(diào)測]。
    4. 鴻蒙開發(fā)指導文檔:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md點擊或者復制轉(zhuǎn)到。

代碼結(jié)構(gòu)解讀

HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿

搜狗高速瀏覽器截圖20240326151547.png

本篇Codelab只對核心代碼進行講解,對于完整代碼,我們會在gitee中提供。

├──entry/src/main/ets                   // 代碼區(qū)
│  ├──common
│  │  ├──constants
│  │  │  └──CommonConstants.ets         // 公共常量類
│  │  └──utils
│  │     ├──AesUtil.ets                 // 加解密工具類
│  │     ├──DataTransformUtil.ets       // 數(shù)據(jù)轉(zhuǎn)換工具類
│  │     ├──Logger.ets                  // 日志打印工具類
│  │     └──PromptUtil.ts               // 彈窗工具類
│  ├──entryability
│  │  └──EntryAbility.ts                // 程序入口類
│  ├──model
│  │  ├──RdbModel.ets                   // 數(shù)據(jù)庫業(yè)務(wù)處理文件
│  │  └──UserTableApi.ets               // 用戶表具體業(yè)務(wù)文件
│  ├──pages
│  │  ├──Login.ets                      // 登錄頁
│  │  ├──Register.ets                   // 注冊頁
│  │  └──Welcome.ets                    // 歡迎頁
│  └──viewmodel
│     └──User.ets                       // 用戶實體類
└──entry/src/main/resources             // 資源文件目錄

關(guān)系型數(shù)據(jù)庫

首先編寫創(chuàng)建表的SQL語句,其中user為表名、id為主鍵并自動遞增、username為用戶名、password為加密后的密碼、authTag為加解密認證信息。

// CommonConstants.ets
  /**
   * 創(chuàng)建表的SQL語句
   */
  static readonly CREATE_TABLE_SQL: string = 'CREATE TABLE IF NOT EXISTS user(' +
    'id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
    'username TEXT NOT NULL, ' +
    'password TEXT NOT NULL, ' +
    'authTag TEXT NOT NULL)';

在RdbModel的構(gòu)造方法中,調(diào)用getRdbStore方法創(chuàng)建數(shù)據(jù)庫。其中STORE_CONFIG為數(shù)據(jù)庫相關(guān)配置,sqlCreateTable為創(chuàng)建user用戶表所需的SQL語句。

// RdbModel.ets
import dataRdb from '@ohos.data.relationalStore';
...
export class RdbModel {
  private rdbStore: dataRdb.RdbStore | null = null;
  private tableName: string = '';
  private sqlCreateTable: string = '';
  ...
  constructor(tableName: string, sqlCreateTable: string, columns: Array< string >) {
    this.tableName = tableName;
    this.sqlCreateTable = sqlCreateTable;
    this.columns = columns;
    this.getRdbStore();
  }

  /**
   * 獲取數(shù)據(jù)庫操作對象rdbStore.
   */
  getRdbStore() {
    let getPromiseRdb = dataRdb.getRdbStore(getContext(), { name: CommonConstants.DATABASE_NAME, securityLevel: dataRdb.SecurityLevel.S1 });
    getPromiseRdb.then(rdbStore = > {
      this.rdbStore = rdbStore;
      this.rdbStore.executeSql(this.sqlCreateTable);
    }).catch((err: Error) = > {
      Logger.error(`getRdbStore err ${JSON.stringify(err)}`);
    });
  }

創(chuàng)建UserTableApi.ets文件,實例化RdbModel創(chuàng)建userTable對象。并對外提供可操作用戶數(shù)據(jù)表的API接口,包括插入數(shù)據(jù)、根據(jù)用戶名查詢用戶信息等方法。

// UserTableApi.ets
export class UserTableApi {
  private userTable = new RdbModel(TABLE_NAME, CREATE_USER_TABLE, COLUMNS);

  /**
   * 將數(shù)據(jù)保存到數(shù)據(jù)庫中
   *
   * @param user 需要保存的User類型的數(shù)據(jù)對象
   */
  insertUserData(user: User) {
    this.userTable.insertData(user);
  }

  /**
   * 根據(jù)用戶名查詢用戶信息
   *
   * @param username 查詢的用戶名
   * @returns 查詢結(jié)果集
   */
  async queryUserByUsername(username: string): Promise< User[] > {
    let resultList: Array< User >;
    // 過濾條件
    let predicates = new dataRdb.RdbPredicates(TABLE_NAME);
    predicates.equalTo('username', username);
    // 將查詢到的結(jié)果封裝成User對應的用戶信息
    let ret = await this.userTable.query(predicates);
    resultList = this.getListFromResultSet(ret);
    return resultList;
  }
  ...
}

密碼加解密

創(chuàng)建AesUtil工具類,封裝加解密相關(guān)邏輯。首先引入@ohos.security.cryptoFramework包,在構(gòu)造方法中初始化加解密算法框架所需的環(huán)境,包括密鑰規(guī)格的選擇、加解密規(guī)格的選擇等。本示例采用對稱AES加解密算法,密鑰長度為256位,分組模式為GCM。具體有以下步驟:

  1. 創(chuàng)建對稱密鑰生成器。
  2. 通過密鑰生成器生成對稱密鑰。
  3. 創(chuàng)建加解密生成器。
  4. 通過加解密生成器加密或解密數(shù)據(jù)。

說明: 對于對稱密鑰、非對稱密鑰、加解密算法模式。

// AesUtil.ets
import cryptoFramework from '@ohos.security.cryptoFramework';
...
class AesUtil {
  private globalCipher: cryptoFramework.Cipher = cryptoFramework.createCipher(CommonConstants.GENERATOR_NAME;
  private globalKey: cryptoFramework.SymKey = null;

  /**
   * 構(gòu)造函數(shù)初始化加解密環(huán)境、生成密鑰
   */
  constructor() {
    let symAlgName = CommonConstants.ENCRYPTION_MODE;
    // 創(chuàng)建對稱密鑰生成器
    let symKeyGenerator = cryptoFramework.createSymKeyGenerator(symAlgName);
    // 通過密鑰生成器和keyMaterialBlob生成256位長度的對稱密鑰
    let keyMaterialBlob = this.genKeyMaterialBlob(CommonConstants.KEY_DATA);
    symKeyGenerator.convertKey(keyMaterialBlob, (err, symKey) = > {
      if (err) {
        Logger.error(`Convert symKey failed, ${err.code}, ${err.message}`);
        return;
      }
      this.globalKey = symKey;
      let cipherAlgName = CommonConstants.GENERATOR_NAME;
      try {
        // 生成加解密生成器
        this.globalCipher = cryptoFramework.createCipher(cipherAlgName);
      } catch (error) {
        Logger.error(`createCipher failed, error is ${JSON.stringify(err)}`);
      }
    });
  }

  // 加密
  async encrypt(content: string, authTag: string): Promise< string > {
    ...
  }

  // 解密 
  async decrypt(content: string, authTag: string): Promise< string > {
    ...
  }
}

密碼加密

由于加密算法采用GCM分組模式,在加密前需要獲取GCM模式加解密所需的參數(shù)GcmParamsSpec。依次生成長度為12字節(jié)、8字節(jié)、16字節(jié)的DataBlob類型的數(shù)據(jù),并封裝成GcmParamsSpec對象。

// AesUtil.ets
class AesUtil {
  ...
  /**
   * 獲取GCM分組加解密所需的參數(shù)
   *
   * @returns 返回加密所需參數(shù)的promise實例
   */
  async genGcmParamsSpec(): Promise< cryptoFramework.GcmParamsSpec > {
    let ivBlob: cryptoFramework.DataBlob = this.genKeyMaterialBlob(CommonConstants.GCM_IV_DATA);
    let aadBlob: cryptoFramework.DataBlob = this.genKeyMaterialBlob(CommonConstants.GCM_AAD_DATA);
    let tagBlob: cryptoFramework.DataBlob = this.genKeyMaterialBlob(CommonConstants.GCM_TAG_DATA);
    let gcmParamsSpec: cryptoFramework.GcmParamsSpec = {
      iv: ivBlob,
      aad: aadBlob,
      authTag: tagBlob,
      algName: `GcmParamsSpec`
    };
    return gcmParamsSpec;
  }

  /**
   * 根據(jù)數(shù)據(jù)組生成DataBlob類型的數(shù)據(jù)
   *
   * @param data 需要封裝的數(shù)據(jù)
   * @returns Blob DataBlob類型的數(shù)據(jù)
   */
  genKeyMaterialBlob(data: Array< number >): cryptoFramework.DataBlob {
    let keyMaterial = new Uint8Array(data);
    return { data: keyMaterial };
  }
}

在AesUtil.ets的encrypt方法中實現(xiàn)密碼加密邏輯。由于本示例加密數(shù)據(jù)量較小,所以這里直接使用update一步完成加密操作。若數(shù)據(jù)量較大,可通過update方法分段加密。主要實現(xiàn)以下步驟:

  1. 調(diào)用Cipher的init方法初始化加密環(huán)境。設(shè)置mode為ENCRYPT_MODE,傳入密鑰和生成的gcmParams。
  2. 將用戶輸入的密碼轉(zhuǎn)換為Uint8Array數(shù)組,進而封裝成DataBlob對象。
  3. 將封裝好的plainTextBlob傳入update方法中完成加密。
  4. 調(diào)用doFinal方法,傳入null,取出加密后的認證信息authTag。
  5. 取出Uint8Array類型的加密數(shù)據(jù)和authTag,轉(zhuǎn)換成Base64類型的字符串,封裝成User類型返回。
// AesUtil.ets
class AesUtil {
  ...
  /**
   * 加密
   *
   * @param content 加密內(nèi)容
   * @returns 返回攜帶密文User對象的promise實例
   */
  async encrypt(content: string): Promise< User > {
    // 初始化加密環(huán)境
    let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
    let gcmParams = await this.genGcmParamsSpec();
    await this.globalCipher.init(mode, this.globalKey, gcmParams);
    let plainTextBlob = {
      // 字符串轉(zhuǎn)Uint8Array
      data: DataTransformUtil.stringToUint8Array(content)
    };
    // 加密
    let updateOutput: cryptoFramework.DataBlob = await this.globalCipher.update(plainTextBlob);
    if (!updateOutput) {
      return Promise.reject('encrypt updateOutput is null');
    }
    let authTag: cryptoFramework.DataBlob = await this.globalCipher.doFinal(null);
    // Uint8Array轉(zhuǎn)base64
    let encryptContent: string = DataTransformUtil.uint8ArrayToBase64(updateOutput.data);
    let authTagContent: string = DataTransformUtil.uint8ArrayToBase64(authTag.data);
    let user = new User(null, ``, encryptContent, authTagContent);
    return user;
  }
}

密碼解密

解密操作與加密類似,主要實現(xiàn)以下步驟:

  1. 調(diào)用Cipher的init方法初始化解密環(huán)境,設(shè)置mode為DECRYPT_MODE,傳入攜帶authTag認證信息的gcmParams。
  2. 將Base64類型密文轉(zhuǎn)換為Uint8Array數(shù)組,進而封裝成DataBlob對象。
  3. 將封裝好的plainTextBlob傳入doFinal方法中完成解密。
  4. 取出Uint8Array類型的解密數(shù)據(jù),轉(zhuǎn)換成字符串并返回。
// AesUtil.ets
class AesUtil {
  ...

  /**
   * 解密
   *
   * @param content 解密內(nèi)容
   * @param authTag GCM 解密所需認證信息內(nèi)容
   * @returns 返回解密內(nèi)容的promise實例
   */
  async decrypt(content: string, authTag: string): Promise< string > {
    // 初始化解密環(huán)境
    let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
    let gcmParams = await this.genGcmParamsSpec();
    let authTagBlob: cryptoFramework.DataBlob = {
      data: DataTransformUtil.base64ToUint8Array(authTag)
    };
    gcmParams.authTag = authTagBlob;
    await this.globalCipher.init(mode, this.globalKey, gcmParams);
    let plainTextBlob: cryptoFramework.DataBlob = {
      // base64轉(zhuǎn)Uint8Array
      data: DataTransformUtil.base64ToUint8Array(content)
    };
    // 解密
    let finalOutput: cryptoFramework.DataBlob = await this.globalCipher.doFinal(plainTextBlob);
    if (!finalOutput) {
      return Promise.reject('decrypt finalOutput is null');
    }
    // Uint8Array轉(zhuǎn)字符串
    let decryptContent = DataTransformUtil.uint8ArrayToString(finalOutput.data);
    return decryptContent;
  }
}

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 字符串
    +關(guān)注

    關(guān)注

    1

    文章

    566

    瀏覽量

    20389
  • 鴻蒙
    +關(guān)注

    關(guān)注

    56

    文章

    2271

    瀏覽量

    42508
  • HarmonyOS
    +關(guān)注

    關(guān)注

    79

    文章

    1949

    瀏覽量

    29758
  • OpenHarmony
    +關(guān)注

    關(guān)注

    25

    文章

    3555

    瀏覽量

    15776
收藏 人收藏

    評論

    相關(guān)推薦

    十六進制字符串AES是如何分組的?

    文章目錄產(chǎn)生的疑問十六進制字符串AES是如何分組的?AES是目前最流行的分組對稱加密算法,在開發(fā)的過程中,無處不在。Intel甚至在他們的芯片中定制了底層指令來讓AES更快地執(zhí)行,所以AES加解密
    發(fā)表于 07-22 08:25

    字符串的表示

    字符串的表示  隨著計算機在文字處理與信息管理中的廣泛應用, 字符串已成為最常用的數(shù)據(jù)類型之一, 許多計算機中都提供字符串操作功能, 一些計算機還給出讀寫字
    發(fā)表于 10-13 17:11 ?3026次閱讀
    <b class='flag-5'>字符串</b>的表示

    C語言教程之字符串加密和解密

    C語言教程之字符串加密和解密,很好的C語言資料,快來學習吧。
    發(fā)表于 04-25 15:49 ?0次下載

    python字符串拼接方式了解

    python字符串拼接的方式 在Python的實際開發(fā)中,很多都需要用到字符串拼接,python中字符串拼接有很多,今天總結(jié)一下: 用+符號拼接 用%符號拼接 用join()方法拼接
    發(fā)表于 12-06 10:09 ?992次閱讀

    什么是復制字符串?Python如何復制字符串

    連續(xù)幾篇文章都在寫 Python 字符串,這出乎我的意料了。但是,有的問題,不寫不行,特別是那種靈機一動想到的問題,最后你發(fā)現(xiàn),很多人根本不懂卻又誤以為自己懂了。那就繼續(xù)刨根問底,探究個明白吧
    發(fā)表于 11-25 10:32 ?2932次閱讀

    strtok拆分字符串

    大家好,我是驚覺,今天聊聊字符串字符串的使用場景非常之多,人機交互和雙機通信都會用到。比如:通過串口向單片機發(fā)送指令,以執(zhí)行操作或配置參數(shù)。單片機讀取傳感器數(shù)據(jù),數(shù)據(jù)格式是字符串。一般GPS數(shù)據(jù)
    發(fā)表于 01-13 15:46 ?8次下載
    strtok拆分<b class='flag-5'>字符串</b>

    字符串函數(shù)重寫練習

    字符串函數(shù)重寫練習:字符串比較、字符串拼接、字符串查找、字符串拷貝、內(nèi)存比較、內(nèi)存拷貝、內(nèi)存初始化、內(nèi)存比較、二維數(shù)組定義及基本使用、位運算
    的頭像 發(fā)表于 05-05 15:02 ?1882次閱讀

    字符串操作

    labview字符串操作
    發(fā)表于 06-28 15:09 ?2次下載

    C語言總結(jié)_字符串全方位練習

    C語言字符串全方位練習,涉及知識點:字符串解析、大小寫判斷、字符串插入、字符串刪除、字符串排序、字符串
    的頭像 發(fā)表于 08-14 09:41 ?1366次閱讀

    一文詳解JavaScript字符串

    JavaScript字符串是原始值。此外,字符串是不可變的。這意味著如果你修改一個字符串,你總是會得到一個新的字符串。原始字符串不會被改變。
    的頭像 發(fā)表于 12-08 16:36 ?1071次閱讀

    python字符串有哪些特定方法

    python字符串序列操作也適用于列表和元組。 python字符串還有獨有方法,即字符串對象的函數(shù),其他對象不可調(diào)用,只有字符串對象可調(diào)用。
    的頭像 發(fā)表于 02-23 15:02 ?608次閱讀

    C語言字符串的引用方式

    在C語言程序中,字符串是存放在字符數(shù)組中的。 2. 用字符數(shù)組存放一個字符串,可以通過數(shù)組名和下標引用字符串中的一個
    的頭像 發(fā)表于 03-10 14:57 ?1709次閱讀

    字符串的相關(guān)知識

    TCL 中的數(shù)據(jù)類型只有一種:字符串。這些字符串可以是字母、數(shù)字、布爾值、標點符號等特殊字符的組合。在某些特殊命令的作用下,字符串可以向其他數(shù)據(jù)類型轉(zhuǎn)換。下面將系統(tǒng)的講解或回顧下
    的頭像 發(fā)表于 03-29 11:41 ?1004次閱讀

    labview掃描字符串怎么用

    LabVIEW 是一種流程化編程語言和開發(fā)環(huán)境,主要用于控制、測量和監(jiān)測系統(tǒng)。在 LabVIEW 中,掃描字符串是一項常見的任務(wù),它允許用戶按照一定的模式從輸入字符串中提取所需的信息。下面我將詳細
    的頭像 發(fā)表于 12-29 10:12 ?1562次閱讀

    labview字符串如何轉(zhuǎn)換為16進制字符串

    在LabVIEW中,將字符串轉(zhuǎn)換為16進制字符串是一個常見的需求,尤其是在處理數(shù)據(jù)通信和硬件接口時。LabVIEW提供了多種方法來實現(xiàn)這一轉(zhuǎn)換,包括使用內(nèi)置函數(shù)、編寫VI(Virtual
    的頭像 發(fā)表于 09-04 15:54 ?606次閱讀