---
# System prepended metadata

title: Crypto

---

# Crypto

[Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)

Crypto.subtle 是 Web Crypto API 的一部分，是 JavaScript 中用於執行加密和解密操作的接口。它提供了一組強大的加密算法，包括
* 對稱密鑰加密算法、
* 非對稱密鑰加密算法、
* 哈希函數和消息身份驗證代碼（MAC）算法等。

Crypto.subtle 的主要作用是提供一種安全的方式來執行加密操作，以便在 Web 應用程序中保護用戶數據的隱私和安全。例如，可以使用 Crypto.subtle 實現以下任務：

* 對稱密鑰加密和解密
* 非對稱密鑰加密和解密
* 計算哈希值
* 計算消息身份驗證代碼
* 生成隨機值
* 密鑰派生和密鑰導出等操作。

Crypto.subtle 支持的加密算法和操作會因實現和瀏覽器而異。例如，可能支持的對稱密鑰加密算法包括 AES、DES、3DES 等。可能支持的非對稱密鑰加密算法包括 RSA、ECDSA 等。

需要注意的是，雖然 Crypto.subtle 提供了一組強大的加密算法和操作，但是在實際使用時需要格外小心。一些加密算法和操作可能會對性能產生重大影響，而一些錯誤的使用方式可能會導致加密漏洞和安全問題。因此，在使用 Crypto.subtle 時應該仔細閱讀相關文檔和指南，並且遵循最佳實踐來確保安全性和可靠性。

## dev

```typescript
// Generate a random 256-bit key
const key = await window.crypto.subtle.generateKey(
  {name: 'AES-CBC', length: 256},
  true, // Extractable
  ['encrypt', 'decrypt'] // Key usages
);

// Convert the message to an ArrayBuffer
const message = new TextEncoder().encode('Hello, world!').buffer;

// Encrypt the message using AES-CBC
const iv = window.crypto.getRandomValues(new Uint8Array(16));
const ciphertext = await window.crypto.subtle.encrypt(
  {name: 'AES-CBC', iv},
  key,
  message
);

// Decrypt the ciphertext using AES-CBC
const decryptedMessage = await window.crypto.subtle.decrypt(
  {name: 'AES-CBC', iv},
  key,
  ciphertext
);

// Convert the decrypted message to a string
const decryptedText = new TextDecoder().decode(decryptedMessage);
console.log(decryptedText); // "Hello, world!"

```

## prod
```typescript
// Generate a random key for HMAC SHA-256
const key = await window.crypto.subtle.generateKey(
  {name: 'HMAC', hash: 'SHA-256', length: 256},
  true, // Extractable
  ['sign', 'verify'] // Key usages
);

// Convert the message to an ArrayBuffer
const message = new TextEncoder().encode('Hello, world!').buffer;

// Calculate the HMAC of the message
const hmac = await window.crypto.subtle.sign(
  {name: 'HMAC', hash: 'SHA-256'},
  key,
  message
);

// Verify the HMAC of the message
const isValid = await window.crypto.subtle.verify(
  {name: 'HMAC', hash: 'SHA-256'},
  key,
  hmac,
  message
);

console.log(isValid); // true

```


在 prod 使用中，需要考慮更多的安全性問題，例如密鑰管理、密鑰交換、密碼學協議等，以確保數據的隱私和安全。


crypto.sign主要用於數據完整性、來源認證和數字證書驗證，而crypto.encrypt主要用於數據保密性和安全通信。在實際應用中，這兩種技術可以結合使用，以提供更全面的數據保護和安全性。

上面的例子:
`Extractable` : 用意是讓生成的key能夠讓之後的crypto api `import` `export` 使用你生成的key
`new TextEncoder()`: 將string轉乘Uint8Array 或 ArrayBuffer ,兩者都是二進制資料格式，差別在於ArrayBuffer 日後無法修，Uint8Array 則是 ArrayBuffer 的視圖日後可以調整資料
```typescript
// importKey 用來 sign 使用，generateKey 則是用來作 decode/encode不能混用
const signkey = crypto.subtle.importKey(
  'raw',
  new TextEncoder().encode('sign_secret'),
  { name: 'HMAC', hash: { name: 'SHA-256' } },
  false,
  ['sign', 'verify']
)
const decodeKey = crypto.subtle.generateKey(
  { name: 'AES-CBC', length: 256 },
  true,
  ['decrypt', 'encrypt']
)

//  sign 用來驗證雙向資料的正確性
const sign = async (data: string) => {
  const message_in = new TextEncoder().encode(data)
  const message_out = new TextEncoder().encode(data)
  const hmc = crypto.subtle.sign(
    { name: 'HMAC', hash: 'SHA-256' },
    await signkey,
    message_in
  )
  const isValidate = crypto.subtle.verify(
    { name: 'HMAC', hash: 'SHA-256' },
    await signkey,
    await hmc,
    message_out
  ) // true
  console.log(await isValidate)
}

// encrypt 增加一層加密讓 TextEncoder 轉換需要多一把鑰匙才能 decode
const decode = async (data: string) => {
  try {
    const message = new TextEncoder().encode(data)
    const iv = crypto.getRandomValues(new Uint8Array(16))
    const encodeMessage = crypto.subtle.encrypt(
      { name: 'AES-CBC', iv },
      await decodeKey,
      message
    )
    const decryptedMessage = crypto.subtle.decrypt(
      { name: 'AES-CBC', iv },
      await decodeKey,
      await encodeMessage
    )
    const result = new TextDecoder().decode(await decryptedMessage) // data
    console.log(result)
  } catch (e) {
    console.log(e)
  }
}

```
```typescript
const data = "Hello, world!";
const uint8array = new TextEncoder().encode(data);
const ArrayBuffer = new TextEncoder().encode(data).buffer;
```

### getRandomValues

如果你只是需要做隨機亂數生成，使用key沒有安全疑慮的話可以用他，因為 getRandomValues 他使用的生成器是  pseudo-random number generator 而不像 subtle generateKey 是採用 truly random number generator,這對效能來說會提升不少。
```javascript
const array = new Uint32Array(10);
self.crypto.getRandomValues(array);

console.log("Your lucky numbers:");
for (const num of array) {
  console.log(num);
}
```
hint : getRandomValues 生成的key不保證他的加密算法沒有安全疑慮的，所以使用前要`注意一下`。

### randomUUID
另一個更輕量的隨機生成數就是 randomUUID ，生成的結構是跟 v4 UUID 是一樣的，所以如果你需要用在data 的 id 剛好格式需要 UUID 的形式就用他吧!!

```typescript
/* Assuming that self.crypto.randomUUID() is available */

let uuid = self.crypto.randomUUID();
console.log(uuid); // for example "36b8f84d-df4e-4d49-b662-bcde71a8764f"
```


## Math.random() 機率分佈不均勻

因為真正的隨機事件其實際上是要不可預測的結果，Math.random採用的是PRNG算法生成隨機seed來達隨機亂數之目的，由PRNG的生成算法是確定的，因此生成的隨機順序列表在理論上是可以的預測的。因此，在某種情況下，使用Math.random() 隨機生成的隨機數分佈可能不均。時間段內，生成的隨機分配可能會偏離均分佈。

```typescript
function randomWithCrypto() {
  const array = new Uint32Array(1)
  crypto.getRandomValues(array)
  return Math.floor(array[0] / Math.pow(2, 32) * 100) // 1-100
}
function randomWithMathRandom() {
  return Math.floor(Math.random() * 100)
}

function crypto1000() {
  let result = {}
  let key
  for (let i = 0; i < 10000; i++) {
    key = randomWithCrypto()
    if (result[key]) {
      result[key]++
    } else {
      result[key] = 1
    }
  }

  return result
}
function mathRandom1000() {
  let result = {}
  for (let i = 0; i < 10000; i++) {
    key = randomWithMathRandom()
    if (result[key]) {
      result[key]++
    } else {
      result[key] = 1
    }
  }
  return result
}

console.time('crypto')
crypto1000()
console.timeEnd('crypto') //crypto: 16.324ms

console.time('Math')
mathRandom1000()
console.timeEnd('Math') // Math: 0.865ms
```



| 方法             | Math.random()  | getRandomValues()|
| ---------------- | --------- | --------------------- |
| 生成數分布均勻度    | 不均勻      |  	均勻           |
| 安全性            | 不安全   | 安全             |
| 10000 次生成時間 (ms)           | 0.8 ~ 1  | 15 以上              |


`生成數分布均勻度` 是指生成的隨機數在 0 到 100 之間的分布情況。由demo可知，Math.random() 生成的隨機數分布不均勻，而 getRandomValues() 生成的隨機數分布均勻。(可以貼範例跑跑看結果ＸＤ)

而 `安全性` 則是指生成的隨機數是否足夠安全，能否用於加密等安全需求場合。`Math.random()`不安全，因為它的生成數容易被猜測。而 `getRandomValues()` 則相對安全，因為它的生成數具有高度的隨機性和不可預測性。



### 額外補充

generateKey VS importKey，兩者都是密鑰提供方式，差別在於importKey 可以將已有的密鑰在做加密提供crypto api去做解析。

```typescript
const key = crypto.subtle.importKey(
  'raw',
  new TextEncoder().encode('my_secret'),
  { name: 'HMAC', hash: { name: 'SHA-256' } },
  false,
  ['sign'],
);

const key = await window.crypto.subtle.generateKey(
  {name: 'HMAC', hash: 'SHA-256', length: 256},
  true, // Extractable
  ['sign', 'verify'] // Key usages
);
```

## library

hmac 他是一個特定obj資料格式，createHmac 用於生成實例，update 用時存放data到 hmac object中，digest 則是將hmac object data轉譯成你想要的資料格式，例如hex或是base64
```typescript
import { createHmac } from 'node:crypto';

function getToken(id: string): string {
  const hmac = createHmac('sha256', 'my_secret')
  hmac.update(JSON.stringify({ id }))
  const token = hmac.digest('hex')
  return token;
}
```

## arraybuffer to string (decode)
```typescript
const key = crypto.subtle.importKey(
  'raw',
  new TextEncoder().encode('Kjhg2365987'),
  { name: 'HMAC', hash: { name: 'SHA-256' } },
  false,
  ['sign', 'verify']
)
const message = new TextDecoder().encoding(JSON.stringify({ id: '1' }))

function toHex(arrayBuffer) {
  return Array.prototype.map
    .call(new Uint8Array(arrayBuffer), (n) => n.toString(16).padStart(2, '0'))
    .join('');
}
const hmac = crypto.subtle.sign(
  'HMAC',
  await key,
  message
)
const token = toHex(hmac)
```Uint16Array

### compare hash
```typescript
import crypto from 'crypto'
const secret = 'alpha'
const string = 'bacon'

const compareHmac = (text) => {
  // from validate data
  const hash = crypto.createHmac('SHA256', secret).update(string).digest('base64')
  const result = crypto.createHmac('SHA256', secret).update(text).digest('base64')
  if (hash == result) {
    console.log('match')
  } else {
    console.log('no match')
  }
}

```

## Uint8Array vs Uint16Array

Uint16Array 跟 Uint8Array 都是一種二進制的格式，差別在於字元中儲存的值大小與記憶體儲存的差異：
* Uint8Array : array 中每個字元佔用 8 bytes ，所以值得範圍會是 0 - 255 (2 的 8 次方)
* Uint16Array : array 中每個字元佔用 8 bytes ，所以值得範圍會是 0 - 65535 (2 的 16 次方)

用途：
* Uint8Array：需要減少內存的情況或是處理值的範圍在 0 - 256內推薦使用，詳情可以查看 查看[utf-8 編碼表](http://www.mytju.com/classcode/tools/encode_utf8.asp) 使用字元的範圍 ，通常簡易圖像數據、音樂資料都會採用以及英文字母轉換。
* Uint16Array ： 用於相對複查的演算與大量圖像演算法會去做使用。

### 使用
Uint8Array 或是 Uint16Array 可以透過 array 用 index方式去修改內容，值得提的是 Uint8Array 跟 Uint16Array 長度是固定的不能做 dynamic length，使用上會是透過 ArrayBuffer去指定長度。

```typescript
let buffer = new ArrayBuffer(16); // 創建一個長度 16 的 buffer

let view = new Uint32Array(buffer); // 將 buffer 當成 32位整數的序列

alert(Uint32Array.BYTES_PER_ELEMENT); // 每個整數 4 個字節

alert(view.length); // 總共存 4 個整數
alert(view.byteLength); // 16，每個字節大小

// 让我们写入一个值
view[0] = 123456;


for(let num of view) {
  alert(num); // 123456，然後 0，0，0（4 個值）
}

```


### 範例

但有時候我們並不會去查看 utf-8 編碼去對照，這是我們可以用 TextEncoder 跟 TextDecoder 用來輸入我們想放的 data就好就不用一個一個字元去對照編碼表，預設情況下 TextEncoder encode出來的結果會是 uint8Arrays ，所以 example 1 跟 example 2 結果一樣
```typescript
let data = 'name'
const encoder = new TextEncoder();
const decoder = new TextDecoder();

<!-- example 1  -->
const uint8Arrays = encoder.encode(data)
<!-- example 2  -->
const uint8Arrays2 = new.Uint8Array(encoder.encode(data))

const decode = new TextDecoder().decode(uint8Arrays) //name
```

### unit8ToString
unit8可以透過 FileReader 去解析，但 FileReader 他是 browser 的 api ，所以在node runtime 中會報 error 要注意。

```typescript
  const largeuint8ArrToString = (uint8arr: Uint8Array, callBack: (e: ProgressEvent<FileReader>) => void) => {
    let bb = new Blob([uint8arr])
    //  FileReader 是 webApi所以只能在 client component 中的 useEffect 中使用
    const reader = new FileReader()
    reader.onload = (e) => {
      callBack(e)
    }
    reader.readAsText(bb)
  }
  largeuint8ArrToString(unitArray8, (e) => console.log(e.target?.result))

```

unit16ToString 待解
