简单加解密数据算法与储存结构设计:ssir coder

在线应用 ateditors 的 WebRTC 实时通信功能中需要分发诸如信令、ICE 服务的用户名密码等需要保密的编码数据。ssir coder即为此用途而设计。

此加密算法使用 PBKDF2 生成 AES-GCM 密钥。然后对保密信息进行加密,使用 hex 或 base64 编码后进行分发、接收解码解密使用。

使用 ArrayBuffer 编码的简单数据格式如下:
AAAA BBBB CCCC DDDD EEEE

AAAA: 32 字节随机填充数据
BBBB CCCC: 双 32 字节随机 AES 初始变量 iv
DDDD: 32 字节随机 PBKDF2 参数 salt
EEEE: 保密信息使用 AES 加密算法加密后的密文

加密实现过程如下:

步骤  操作

1    调用 crypto.getRandomValues 生成固定大小的 PBKDF2 参数 salt
2    设定 hash 算法固定为 SHA-512
3    调用 crypto.subtle.digest 对前述确定的 salt 使用 hash 算法生成 PBKDF2 的密钥数据
4    使用 crypto.subtle.importKey 导入 PBKDF2 密钥数据生成 PBKDF2 初始密钥材料
5    设定 iterations 为一个固定的足够大的自然数
6    设定 AesDerivedKeyParams 密钥长度 length 为固定的 256
7    使用 PBKDF2 密钥材料与 salt/iterations/hash/length 调用 crypto.subtle.deriveKey 生成 AES-GCM 密钥
8    调用 crypto.getRandomValues 生成固定大小的 AesGcmParams 初始变量 iv
9    使用前述密钥与初始变量调用 crypto.subtle.encrypt 加密保密信息为密文 cipher
a    使用 crypto.getRandomValues 生成足够大小的 Uint8Array 以保存前述所有信息数据
b    跳过起始大小的随机填充数据依次填充 iv/salt/cipher 进 Uint8Array中

解密实现过程基本就是前述加密实现过程的逆操作:

步骤  操作

1    提取 salt
2    设定 hash 算法固定为 SHA-512
3    调用 crypto.subtle.digest 对提取的 salt 使用 hash 算法生成 PBKDF2 的密钥数据
4    调用 crypto.subtle.importKey 导入 PBKDF2 密钥数据生成 PBKDF2 初始密钥材料
5    设定 iterations 为一个固定的足够大的自然数
6    设定 AesDerivedKeyParams 密钥长度 length 为固定的 256
7    使用 PBKDF2 密钥材料与 salt/iterations/hash/length 调用 crypto.subtle.deriveKey 生成 AES-GCM 密钥
8    提取 AesGcmParams 初始变量 iv
9    提取密文 cipher
a    使用前述密钥与初始变量调用 crypto.subtle.decrypt 解密密文 cipher 为保密信息数据

实现的样例代码:

// The ArrayBuffer format of wrap coder:
// AAAA BBBB CCCC DDDD EEEE
// AAAA: 32 bytes random for padding.
// BBBB CCCC: double 32 bytes random AES iv.
// DDDD: 32 bytes random PBKDF2 salt.
// EEEE: AES encryption data of secure message.
export const wrapcoder = async (secureBuffer: ArrayBuffer) => {
  let wrap = new Uint8Array(keySize);
  if (secureBuffer) {
    const keySize = 32;
    const salt = crypto.getRandomValues(new Uint8Array(keySize));
    const hash = 'SHA-512';
    const keyData = await crypto.subtle.digest(hash, salt);
    const keyMaterial = await crypto.subtle.importKey(
      "raw",
      keyData,
      {
        name: "PBKDF2"
      },
      false,
      [ "deriveKey" ]
    );
    const iterations = 225519;
    const keyLength = keySize * 8;
    const wrappingKey = await crypto.subtle.deriveKey(
      {
        "name": "PBKDF2",
        salt,
        iterations,
        hash
      },
      keyMaterial,
      {
        "name": "AES-GCM",
        "length": keyLength,
      },
      true,
      [ "encrypt" ]
    );
    const iv = crypto.getRandomValues(new Uint8Array(keySize * 2));
    const cipher = await crypto.subtle.encrypt(
      {
        name: "AES-GCM",
        iv
      },
      wrappingKey,
      secureBuffer
    );
		// AAAA BBBB CCCC DDDD EEEE(dynamic length)
    wrap = crypto.getRandomValues(new Uint8Array(keySize * 4 + cipher.byteLength));
		// BBBB CCCC: double 32 bytes random AES iv.
    wrap.set(iv, keySize);
		// DDDD: 32 bytes random PBKDF2 salt.
    wrap.set(salt, keySize * 3);
		// EEEE: AES encryption data of secure message.
    wrap.set(new Uint8Array(cipher), keySize * 4);
  }
  return wrap;
}

export const unwrapcoder = async (wrappedBuffer: ArrayBuffer) => {
  const buffer = new Uint8Array(wrappedBuffer);
  const keySize = 32;
  const salt = buffer.subarray(keySize * 3, keySize * 4);
  const hash = 'SHA-512';
  const keyData = await crypto.subtle.digest(hash, salt);
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    keyData,
    {
      name: "PBKDF2"
    },
    false,
    [ "deriveBits", "deriveKey" ]
  );
  const iterations = 225519;
  const keyLength = keySize * 8;
  const unwrappingKey = await crypto.subtle.deriveKey(
    {
      "name": "PBKDF2",
      salt,
      iterations,
      hash
    },
    keyMaterial,
    {
      "name": "AES-GCM",
      "length": keyLength,
    },
    true,
    [ "decrypt" ]
  );
  const iv = buffer.subarray(keySize, keySize * 3);
  const cipher = buffer.subarray(keySize * 4);
  const secureBuffer = await crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv
    },
    unwrappingKey,
    cipher
  );
	return secureBuffer;
}

应用 ateditors 实时通信保密数据编码 coder 如下:

eyJpY2UiOnsidXJsIjoic3R1bjphbmZsZXh0LmNvbTo1MTIxNSJ9LCJsYWJlbCI6InRlc3QiLCJzaWduYWxpbmciOnsiZ3JvdXAiOiJmaXJlZm94IiwidXJsIjoid3NzOi8vYW5mbGV4dC5jb206NTEyMTcifSwic3NpciI6IjdCT09PWXNUN0VvTE4tY05VSUlZdTZUcFNOQ3ZWVFZtZ2dCSE5DeVY0Tjd6dE1VOXJ4TlJYeWl2LUJtZTlBMzg3WHljYUNSU1d0RWRKNXRBUlY3UFkxZkhEYlV3UW9sNVBLZXg0WHo0anZ4QnRxRGNmSlFDVEh2RnhDWmprdGc0bk9qWGhOdkxjN252cjIwdWtFeFBhWkFhVFhsYjFpVGlLV0RCekZZQzZJTXVHVTVQOXo0MVdaSF83SVZLRFZjWkJfalhDRjVzMmxIQnNDNEZRVUxLbjcyTDVGTmJjZlRGUmxhalFDc0o1eVBtM2pBSnVLTHAtVERLM0UyQmh3by1jUUN5UWd5ME1UdDIxN0YwWW16T3RmWEd4SlgxcC1GTW1CcmctcEFXbFp1ZnBnQld4c0hVUmZSeXAtNTZuMmN0VXl6Z2lrQkZRQ0dhQnUxc0ZEZnowaHVyd0EifQ

粘贴以上 coder 到 ateditors 实时通信功能 option 的 code 字段,点击 Apply coder 按钮解码填充参数字段。

然后点击 Connect signaling 按钮。连接信令服务器后,就可以呼叫在线的群组成员进行 WebRTC 通信。

连接信令服务器后选择在线的群组成员:

选择到可用的在线成员后,点击 call 按键进行呼叫:

呼叫成功后,可以进行文字通信与文件传输:

29

Top articles