apikey_crypto.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. """
  2. API Key 加密/解密工具(纯标准库,无第三方依赖)
  3. 算法:XOR 密钥流 + 字节循环位移 + Base64 URL-safe 编码
  4. 加密流程:
  5. 1. 用 SECRET_KEY 通过 SHA-256 派生密钥流(hashlib,标准库)
  6. 2. 明文每字节与密钥流对应字节 XOR
  7. 3. 每字节循环左移 (i % 5 + 1) 位
  8. 4. Base64 URL-safe 编码(无 = 填充)
  9. 解密流程(完全对称,逆序):
  10. 1. Base64 URL-safe 解码
  11. 2. 每字节循环右移 (i % 5 + 1) 位
  12. 3. 与相同密钥流 XOR
  13. 4. UTF-8 解码
  14. 解密示例(JavaScript):
  15. 见文件末尾注释
  16. """
  17. from __future__ import annotations
  18. import base64
  19. import hashlib
  20. import os
  21. from typing import Optional
  22. def _derive_keystream(key: str, length: int) -> bytes:
  23. """
  24. 从 key 派生指定长度的密钥流。
  25. 原理:反复对 key + 块索引 做 SHA-256,拼接直到够用。
  26. """
  27. stream = bytearray()
  28. block = 0
  29. key_bytes = key.encode("utf-8")
  30. while len(stream) < length:
  31. h = hashlib.sha256(key_bytes + block.to_bytes(4, "big")).digest()
  32. stream.extend(h)
  33. block += 1
  34. return bytes(stream[:length])
  35. def _rotl8(byte: int, n: int) -> int:
  36. """8 位循环左移 n 位。"""
  37. n = n % 8
  38. return ((byte << n) | (byte >> (8 - n))) & 0xFF
  39. def _rotr8(byte: int, n: int) -> int:
  40. """8 位循环右移 n 位。"""
  41. n = n % 8
  42. return ((byte >> n) | (byte << (8 - n))) & 0xFF
  43. def _get_key() -> str:
  44. key = os.environ.get("APIKEY_ENCRYPT_KEY", "")
  45. if not key:
  46. raise RuntimeError("APIKEY_ENCRYPT_KEY 未配置")
  47. return key
  48. def encrypt_api_key(plaintext: str) -> str:
  49. """将 API Key 明文加密为 Base64 URL-safe 密文字符串。"""
  50. data = plaintext.encode("utf-8")
  51. keystream = _derive_keystream(_get_key(), len(data))
  52. result = bytearray(len(data))
  53. for i, byte in enumerate(data):
  54. xored = byte ^ keystream[i]
  55. result[i] = _rotl8(xored, i % 5 + 1)
  56. return base64.urlsafe_b64encode(result).rstrip(b"=").decode("ascii")
  57. def decrypt_api_key(ciphertext: str) -> str:
  58. """将 Base64 URL-safe 密文解密为 API Key 明文。"""
  59. # 补回 Base64 padding
  60. rem = len(ciphertext) % 4
  61. if rem:
  62. ciphertext += "=" * (4 - rem)
  63. data = base64.urlsafe_b64decode(ciphertext)
  64. keystream = _derive_keystream(_get_key(), len(data))
  65. result = bytearray(len(data))
  66. for i, byte in enumerate(data):
  67. unshifted = _rotr8(byte, i % 5 + 1)
  68. result[i] = unshifted ^ keystream[i]
  69. return result.decode("utf-8")
  70. def try_encrypt(plaintext: Optional[str]) -> Optional[str]:
  71. """安全封装:None 直接返回 None,加密失败返回 None。"""
  72. if not plaintext:
  73. return None
  74. try:
  75. return encrypt_api_key(plaintext)
  76. except Exception:
  77. return None
  78. # ─────────────────────────────────────────────────────────────────────────────
  79. # JavaScript 解密实现(供调用方参考)
  80. # ─────────────────────────────────────────────────────────────────────────────
  81. #
  82. # async function deriveKeystream(key, length) {
  83. # const enc = new TextEncoder();
  84. # const keyBytes = enc.encode(key);
  85. # const stream = [];
  86. # let block = 0;
  87. # while (stream.length < length) {
  88. # const blockBytes = new Uint8Array(4);
  89. # new DataView(blockBytes.buffer).setUint32(0, block, false); // big-endian
  90. # const input = new Uint8Array([...keyBytes, ...blockBytes]);
  91. # const hash = await crypto.subtle.digest('SHA-256', input);
  92. # stream.push(...new Uint8Array(hash));
  93. # block++;
  94. # }
  95. # return stream.slice(0, length);
  96. # }
  97. #
  98. # function rotr8(byte, n) {
  99. # n = n % 8;
  100. # return ((byte >>> n) | (byte << (8 - n))) & 0xFF;
  101. # }
  102. #
  103. # function base64urlDecode(str) {
  104. # str = str.replace(/-/g, '+').replace(/_/g, '/');
  105. # while (str.length % 4) str += '=';
  106. # return Uint8Array.from(atob(str), c => c.charCodeAt(0));
  107. # }
  108. #
  109. # async function decryptApiKey(ciphertext, secretKey) {
  110. # const data = base64urlDecode(ciphertext);
  111. # const keystream = await deriveKeystream(secretKey, data.length);
  112. # const result = new Uint8Array(data.length);
  113. # for (let i = 0; i < data.length; i++) {
  114. # const unshifted = rotr8(data[i], i % 5 + 1);
  115. # result[i] = unshifted ^ keystream[i];
  116. # }
  117. # return new TextDecoder().decode(result);
  118. # }
  119. #
  120. # // 使用示例:
  121. # // const apiKey = await decryptApiKey(encryptedValue, 'your_secret_key');
  122. #
  123. # ─────────────────────────────────────────────────────────────────────────────
  124. # Python 解密示例(调用方如果用 Python)
  125. # ─────────────────────────────────────────────────────────────────────────────
  126. #
  127. # import base64, hashlib
  128. #
  129. # def decrypt_api_key(ciphertext: str, secret_key: str) -> str:
  130. # rem = len(ciphertext) % 4
  131. # if rem:
  132. # ciphertext += '=' * (4 - rem)
  133. # data = base64.urlsafe_b64decode(ciphertext)
  134. # # 派生密钥流
  135. # stream, block = bytearray(), 0
  136. # key_bytes = secret_key.encode()
  137. # while len(stream) < len(data):
  138. # stream.extend(hashlib.sha256(key_bytes + block.to_bytes(4, 'big')).digest())
  139. # block += 1
  140. # keystream = bytes(stream[:len(data)])
  141. # # 解密
  142. # result = bytearray(len(data))
  143. # for i, byte in enumerate(data):
  144. # n = i % 5 + 1
  145. # unshifted = ((byte >> n) | (byte << (8 - n))) & 0xFF
  146. # result[i] = unshifted ^ keystream[i]
  147. # return result.decode('utf-8')