Flutter 开发常用加密算法的实现

算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。

一个算法的优劣可以用空间复杂度时间复杂度来衡量。空间复杂度 (Space Complexity) 是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。比如直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。而一般的递归算法就要有O(n)的空间复杂度,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。在计算机科学中,时间复杂性,又称时间复杂度,算法时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。

算法中的指令描述的是一个计算,当其运行时能从一个初始状态和(可能为空的)初始输入开始,经过一系列有限而清晰定义的状态,最终产生输出停止于一个终态。一个状态到另一个状态的转移不一定是确定的。随机化算法在内的一些算法,包含了一些随机输入。

在了解了算法概念后,想必对算法有了初识。在这里小编就来讲讲 Flutter 开发常用哪些加密算法。

首先,推荐学习项目 dart_crypto,它集成了 Base64, 32/16 Bits MD5, AES, RSA 等算法。下面讲解一下各算法的实现与使用。

Base64

  • Base64 Algorithm
  /// Converts a string to base64.
  static yf_base64Encode(String string) {
    if (string == null) throw new ArgumentError("The argument is null");

    // get a base64 encoder instance.
    var encoder = new Base64Encoder();

    // utf8 encoding.
    var list = utf8.encode(string);
    // encode a string to Base64.
    var encodedString = encoder.convert(list);

    return encodedString;
  }

  /// Converts a base64 encoded string to a string or a `Uint8List`.
  static yf_base64Decode(String encodedString, {bool createUint8List = false}) {
    if (encodedString == null) throw new ArgumentError("encodedString is null");

    // get a base64 decoder instance.
    var decoder = Base64Decoder();

    // decode a base64 encoded string to a List of bytes.
    var bytes = decoder.convert(encodedString);

    if (createUint8List) {
      return createUint8ListFromList(bytes);
    }

    var output = utf8.decode(bytes);

    return output;
  }

  /// Converts a List of bytes to a base64 encoded string.
  static base64EncodeList(List<int> bytes) {
    if (bytes == null) throw new ArgumentError("The list is null");

    // get a base64 encoder instance
    var encoder = new Base64Encoder();

    // encode a List of bytes - use line breaks
    var out = encoder.convert(bytes);

    return out;
  }

  /// Converts a base64 encoded List of bytes to a string or a `Uint8List`.
  static base64DecodeList(List<int> bytes, {bool createUint8List = false}) {
    if (bytes == null) throw new ArgumentError("The list is null");

    // get a base64 decoder instance
    var decoder = Base64Decoder();

    var input = new String.fromCharCodes(bytes);
    // decode a Base64 encoded list
    var result = decoder.convert(input);

    if (createUint8List) {
      return createUint8ListFromList(result);
    }

    var output = utf8.decode(result);

    return output;
  }
  • Base64 Usage
try {

    // Base64 - Encode
    final base64Encoded = crypto.DYFCryptoProvider.yf_base64Encode(plainText);
    print("[Base64] Encode: " + base64Encoded);

    // Base64 - Decode
    final base64Decoded = crypto.DYFCryptoProvider.yf_base64Decode(base64Encoded);
    print("[Base64] Decode: " + base64Decoded);

} catch (e) {

    print("e: $e");
}

MD5

  • MD5 Algorithm
/// Creates a hash value with md5.
  static md5Encode(String input) {
    if (input == null) throw new ArgumentError("The input is null");

    var bytes = utf8.encode(input); // data being hashed
    var digest = md5.convert(bytes);

    return digest.toString();
  }

  /// Creates a 16 bits hash value with md5.
  static bit16md5Enconde(String input) {
    var hash = md5Encode(input);
    return hash.substring(8, 24);
  }
  • MD5 Usage
try {

    // MD5 - 32 Bits Encode
    final md5Hash = crypto.DYFCryptoProvider.md5Encode(plainText);
    print("[MD5] Hash: " + md5Hash);

    // MD5 - 16 Bits Encode
    final md5b16hash = crypto.DYFCryptoProvider.bit16md5Enconde(plainText);
    print("[MD5] 16 Bits Hash: " + md5b16hash);

} catch (e) {

    print("e: $e");
}

AES

  • AES Key
  /// Private. The length for AES key is 128 bits, 192 bits, 256 bits.
  static Uint8List _getAESKey(String key, int blockSize) {
    var keyData = createUint8ListFromList(utf8.encode(key));

    var length = blockSize ~/ 8;
    var output = createUint8ListFromList(List.generate(length, (i) => 0));

    int count = Math.min(keyData.lengthInBytes, output.lengthInBytes);
    for (var i = 0; i < count; i++) {
      output[i] = keyData[i];
    }

    return output;
  }
  • AES Algorithm
  /// Returns a cipher text with `AES` algorithm.
  /// The length for AES key is 128 bits, 192 bits, 256 bits.
  static aesEncrypt(String message, String key, {int blockSize: 128}) {
    if (message == null || key == null)
      throw new ArgumentError("message or key is null");

    var keyData = _getAESKey(key, blockSize);
    var aes = AES.fromBytes(keyData);
    var ciphertext = aes.encode(message);

    return ciphertext;
  }

  /// Returns a decoded text with `AES` algorithm.
  /// The length for AES key is 128 or 192 or 256 bits.
  static aesDecrypt(String ciphertext, String key, {int blockSize: 128}) {
    if (ciphertext == null || key == null)
      throw new ArgumentError("ciphertext or key is null");

    var keyData = _getAESKey(key, blockSize);
    var aes = AES.fromBytes(keyData);
    var decryptedText = aes.decode(ciphertext);

    return decryptedText;
  }
  • AES Usage
try {

    // AES - Key
    final aesKey = "smMQI8dMK2nOMUR0TdpBYQUnLpbW8kjHrdy86WtU6eB1Ff6mYveYzezopmbjwBZEjPQmg";
    print("[AES] Key: " + aesKey);

    // AES - Encrypt
    String aesEncryptedText = crypto.DYFCryptoProvider.aesEncrypt(plainText, aesKey);
    print("[AES] Encrypted Text: " + aesEncryptedText);

    // AES - Decrypt
    String aesDecryptedText = crypto.DYFCryptoProvider.aesDecrypt(aesEncryptedText, aesKey);
    print("[AES] Decrypted Text: " + aesDecryptedText);

} catch (e) {

    print("e: $e");
}

RSA

  • RSA Key Formatter
// Converts the key to the content of pem for RSA
class RSAKeyFormatter {
  static formatRSAPublicKey(String publicKey) {
    if (publicKey == null) return null;

    var buffer = new StringBuffer();
    buffer.write("-----BEGIN PUBLIC KEY-----\n");

    final length = publicKey.length;
    int count = 0;

    for (var i = 0; i < length; i++) {
      var c = publicKey.codeUnitAt(i);
      var s = String.fromCharCode(c);
      if (s == "\n" || s == "\r") {
        continue;
      }
      buffer.writeCharCode(c);
      if (++count == 64) {
        buffer.write("\n");
        count = 0;
      }
    }

    buffer.write("\n-----END PUBLIC KEY-----\n");

    return buffer.toString();
  }

  static formatRSAPrivateKey(String privateKey) {
    if (privateKey == null) return null;

    var buffer = new StringBuffer();
    buffer.write("-----BEGIN PRIVATE KEY-----\n");

    final length = privateKey.length;
    int count = 0;

    for (var i = 0; i < length; i++) {
      var c = privateKey.codeUnitAt(i);
      var s = String.fromCharCode(c);
      if (s == "\n" || s == "\r") {
        continue;
      }
      buffer.writeCharCode(c);
      if (++count == 64) {
        buffer.write("\n");
        count = 0;
      }
    }

    buffer.write("\n-----END PRIVATE KEY-----\n");

    return buffer.toString();
  }
}
  • RSA Block
// Calculates the number of block processing times.
class RSABlock {
  final data;
  final int size;

  RSABlock(this.data, this.size);

  get source => data;
  get blockSize => size;

  get blockCount {
    int dataLength = data.length;

    var result = dataLength / size;
    var count = dataLength ~/ size;

    if (result > count) {
      count += 1;
    }

    return count;
  }
}
  • RSA Algorithm
  /// Returns a cipher text with `RSA` algorithm.
  static rsaEncrypt(String message, String publicKey) {
    if (message == null || publicKey == null)
      throw new ArgumentError("message or publicKey is null");

    String pubKey = RSAKeyFormatter.formatRSAPublicKey(publicKey);
    KeyPair pair = KeyPair.parsePem(pubKey);
    int blockSize = pair.bytesize - 11;

    var builder = BytesBuilder();

    var data = utf8.encode(message);
    var rb = RSABlock(data, blockSize);
    int count = rb.blockCount;

    for (var i = 0; i < count; i++) {
      int dataLength = data.length;

      int start = i * blockSize;
      int bufferSize = Math.min(blockSize, dataLength - start);
      int end = start + bufferSize;
      var subdata = data.sublist(start, end);

      var bytes = pair.encrypt(subdata);
      builder.add(bytes);
    }

    var ciphertext = base64Encode(builder.toBytes());

    return ciphertext;
  }

  /// Returns a decoded text with `RSA` algorithm.
  static rsaDecrypt(String ciphertext, String privateKey) {
    if (ciphertext == null || privateKey == null)
      throw new ArgumentError("ciphertext or privateKey is null");

    String privKey = RSAKeyFormatter.formatRSAPrivateKey(privateKey);
    KeyPair pair = KeyPair.parsePem(privKey);
    int blockSize = pair.bytesize;

    var builder = BytesBuilder();

    var data = base64Decode(ciphertext);
    var rb = RSABlock(data, blockSize);
    int count = rb.blockCount;

    for (var i = 0; i < count; i++) {
      int dataLength = data.length;

      int start = i * blockSize;
      int bufferSize = Math.min(blockSize, dataLength - start);
      int end = start + bufferSize;
      var subdata = data.sublist(start, end);

      var bytes = pair.decrypt(subdata);
      builder.add(bytes);
    }

    var decryptedText = utf8.decode(builder.toBytes());

    return decryptedText;
  }

  /// Returns a signature with `RSA` algorithm.
  static rsaSign(String message, String privateKey) {
    if (message == null || privateKey == null)
      throw new ArgumentError("message or privateKey is null");

    String privKey = RSAKeyFormatter.formatRSAPrivateKey(privateKey);
    KeyPair pair = KeyPair.parsePem(privKey);

    var msgBytes = createUint8ListFromList(utf8.encode(message));
    var signBytes = pair.sign(msgBytes);

    var sign = base64Encode(signBytes);

    return sign;
  }

  /// Verifies a signature with `RSA` algorithm.
  /// If true, the signature is correct, otherwise, signing failed.
  static rsaVerify(String signature, String message, String publicKey) {
    if (signature == null || message == null || publicKey == null)
      throw new ArgumentError("signature, message or publicKey is null");

    String pubKey = RSAKeyFormatter.formatRSAPublicKey(publicKey);
    KeyPair pair = KeyPair.parsePem(pubKey);

    var signBytes = base64Decode(signature);
    var msgBytes = createUint8ListFromList(utf8.encode(message));
    bool ret = pair.verify(signBytes, msgBytes);

    return ret;
  }
  • RSA Usage
// 公钥
final publicKey =
"""MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmPW2SwJFldGVB1SM82VYvSZYR
F1H5DREUiDK2SLnksxHAV/roC1uB44a4siUehJ9AKeV/g58pVrjhX3eSiBh9Khom
/S2hEWF2n/6+lqqiwQi1W5rjl86v+dI2F6NgbPFpfesrRjWD9uskT2VX/ZJuMRLz
8VPIyQOM9TW3PkMYBQIDAQAB""";

// 私钥 (pkcs8)
final privateKey =
"""MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKY9bZLAkWV0ZUHV
IzzZVi9JlhEXUfkNERSIMrZIueSzEcBX+ugLW4HjhriyJR6En0Ap5X+DnylWuOFf
d5KIGH0qGib9LaERYXaf/r6WqqLBCLVbmuOXzq/50jYXo2Bs8Wl96ytGNYP26yRP
ZVf9km4xEvPxU8jJA4z1Nbc+QxgFAgMBAAECgYArZVW5PXO3HE9ihBUSyVlqNrdp
9sB7VyHiTjuOwiVkwiocH9trv6s/mPmONVLjSJOZ2FYEl4Nw8yaIDrfUFJrvhdbh
HJnwkO27Wo5jEfm2qGCwgQNtUACoIH637LXfP81v5I7eZtEa7kfO8Axpp3czvO1H
dIAlOI8rU4jb3fB1cQJBANLgfHd/CDro1gtvTrUeTw/lqsKVScGiHn+pmT+THed6
ftJ2MAJVcL/0H8+fFN5mRypCL7LQyPO48dTmfY9PbocCQQDJz8xZGq2BNAd3gSrN
i3q++SEyjRPzDfr8AGJBJF8qtslcSYrVB/jjPx/qNNlMxOoXnpozBojzVTO3UirM
J/wTAkEAzb930YOhPREGHnwImFCtJT6ZYGcWYpXSGg8Y1d2tlLeA28myx+QjMTZ4
fzOgwemaz9FqBpcNKjctxOLqaRRAKwJAXPZwznbgh8zcx6rjea2PjFscdLnR/7tn
6x+OIy3K/NUYan+iCUHT33JblDpmAtwObXTs2SZgfZ645PBfsI2WqwJAGJxnG8+w
iCnzN0CIZvG96tfOZmz0lkM4NSHDwdCSbagJlZccOtodpn00Dzy+l0t+oFe0Xm3R
A0WkPzQX/seO0Q==""";
try {

    // RSA - Encrypt
    String rsaEncryptedText = crypto.DYFCryptoProvider.rsaEncrypt(plainText, publicKey);
    print("[RSA] Encrypted Text: " + rsaEncryptedText);

    // RSA - Decrypt
    String rsaDecryptedText = crypto.DYFCryptoProvider.rsaDecrypt(rsaEncryptedText, privateKey);
    print("[RSA] Decrypted Text: " + rsaDecryptedText);

    // RSA - Sign
    String signature = crypto.DYFCryptoProvider.rsaSign(plainText, privateKey);
    print("[RSA] Signature: " + signature);

    // RSA - Verify
    bool ret = crypto.DYFCryptoProvider.rsaVerify(signature, plainText, publicKey);
    print("[RSA] Signature Verification: " + ret.toString());

} catch (e) {

    print("e: $e");
}

Sample

import 'dart:convert';
import 'dart:math' show Random;
import './crypto/crypto_provider.dart' as crypto;

// Updated and unreal keys.
class KeyConstants {
  
  static final kComm = "##@...#FiQKBgQCmPW2SwJFl}";
  
  static final kPublic =
      "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmPW2SwJFldGVB1SM82VYvSZYR...8VPIyQOM9TW3PkMYBQIDAQAB";
  
  static final kPrivate =
      "MIICXAIBAAKBgQCmPW2SwJFldGVB1SM82VYvSZYRF1H5DREUiDK2SLnksxHAV/ro...CZWXHDraHaZ9NA88vpdLfqBXtF5t0QNFpD80F/7HjtE=";
}

class RandomObjectGenerator {
  
  int genRandomNumber() {
    var max = 1 << 32;
    return Random().nextInt(max);
  }

  String genRandomString({int length: 20}) {
    var buffer = StringBuffer();

    for (var i = 0; i < length; i++) {
      int r = new Random().nextInt(2);

      var s = (r == 1 ? "A" : "a");
      int start = s.codeUnitAt(0);
      int c = start + Random().nextInt(26);

      buffer.writeCharCode(c);
    }

    return buffer.toString();
  }
  
}

abstract class BaseStringUtils {
  String urlEncode(String s);
  String urlDecode(String s);
  String apiEncode(String s);
  String apiDecode(String s);
}

class StringUtils extends RandomObjectGenerator implements BaseStringUtils {
  
  @override
  String genRandomString({int length: 20}) => super.genRandomString(length: length);

  String urlEncode(String s) {
    return Uri.encodeQueryComponent(s);
  }

  String urlDecode(String s) {
    return Uri.decodeQueryComponent(s);
  }

  String apiEncode(String s) {
    if (s == null) throw ArgumentError("The input is null");
    if (s.isEmpty) return s;

    try {
      String randomKey = genRandomString();
      print("randomKey: $randomKey");
      String middleKey = randomKey + KeyConstants.kComm;
      print("middleKey: $middleKey");

      String realKey = crypto.DYFCryptoProvider.bit16md5Enconde(middleKey);
      String mParam = crypto.DYFCryptoProvider.aesEncrypt(s, realKey);

      var middleMap = Map();
      middleMap["p"] = mParam;
      middleMap["k"] = randomKey;
      var jp = json.encode(middleMap);
      print("jp: $jp");

      String ciphertext = crypto.DYFCryptoProvider.rsaEncrypt(jp, KeyConstants.kPublic);
      print("ciphertext: $ciphertext");

      return ciphertext;
    } catch (e) {
      print("e: $e");
    }

    return null;
  }

  String apiDecode(String s) {
    if (s == null) throw ArgumentError("The input is null");
    if (s.isEmpty) return s;

    try {
      String data = crypto.DYFCryptoProvider.rsaDecrypt(s, KeyConstants.kPrivate);

      var map = json.decode(data);
      var mParam = map["p"];
      var randomKey = map["k"];
      print("randomKey: $randomKey");

      String middleKey = randomKey + KeyConstants.kComm;
      print("middleKey: $middleKey");

      String realKey = crypto.DYFCryptoProvider.bit16md5Enconde(middleKey);
      String decodedText = crypto.DYFCryptoProvider.aesDecrypt(mParam, realKey);

      return decodedText;
    } catch (e) {
      print("e: $e");
    }

    return null;
  }
  
}

总结:写的有不好的地方希望大家指出,我会更正,大家有什么看不明白的,也可以在评论里面提问,我会尽力解答。


点赞+关注,第一时间获取技术干货和最新知识点,谢谢你的支持!

最后祝大家生活愉快~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281