从浏览器隐藏 API 密钥

问题描述 投票:0回答:2

我有一个 API 密钥,用于检索浏览器上显示的信息。我遇到的问题是,使用 Chrome - Inspect Element - Source,可以查看 API 密钥。代码如下所示:

<meta charset="utf-8">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <link href="/styles.css" rel="stylesheet" type="text/css" />
        <script type="text/javascript">
        //<![CDATA[
         var dbtKey = "<?php echo $apiKey; ?>"; 

$apiKey
是一个位于我的config.php文件中的var:
$apiKey = 'my key'

有什么办法可以阻止显示此内容吗?

javascript php
2个回答
0
投票

您需要通过 PHP 处理所有请求并将结果作为 JSON 返回,您可以在客户端进行处理。

为了获得额外的安全性,请使用 SSL,这将有助于抵御外部威胁,但无助于抵御内部威胁。


0
投票

混淆是 JavaScript 中唯一可以实现的事情。请注意,这并不能真正防止数据被抓取,它只会让它变得非常烦人。我使用的 .js 文件实际上是一个 PHP 脚本,步骤如下:

  1. 使用非常严格的 CORS 标头(以防止其他网站直接使用)。
  2. 如果可能的话,限制接受的
    referer
    sec-fetch-*
    标头(并且如果标头对于任何和所有合法用途都保证)。
  3. 在 PHP 中,生成 AES-256-GCM 密钥 (
    $aesKey
    ) 和 IV (
    $aesIV
    ),并使用原始输出加密您的 API 密钥。将标签 (
    $aesTag
    ) 附加到密钥末尾,然后对其进行 Base64 编码 (
    $aesOut
    )。附加标签是因为 Subtle Crypto for JS 期望它位于加密数据的末尾。
  4. $aesKey
    分成小块,长度不要超过
    strlen($aesIV)
  5. 为每个
    $aesKey
    生成小写字符的随机字符串,并为
    $aesIV
    生成一个随机字符串。 (
    $rndStr1
    2
    ,...,
    $rndStrIV
    )。我在自己的实现中使用了 12 到 16 之间的随机长度。
  6. 这就是聪明的地方开始的地方。构造一个
    Server-Timing
    标头,其中包含: “
    $rndStrIV
    ;desc=
    base64_encode($aesIV)
    ;dur=
    strlen($aesTag)
    ,
    $rndStr1
    ;描述=
    base64_encode($aesKey1)
    ,
    $rndStr2
    ;描述=
    base64_encode($aesKey2)
    ,
    $rndStr3
    ;描述=
    base64_encode($aesKey3)
    "
    注意: 您应该将“dur”val 随机附加到这些条目之一以增加复杂性。您还应该打乱这些行的顺序。
  7. 是时候吐出实际的 JavaScript 了。您需要两个辅助函数:一个用于从单个 Base64 字符串或 Base64 字符串数组转换为
    ArrayBuffer
    ,另一个用于扫描
    performance.getEntriesByType()
    寻找
    serverTiming
    对象。然后,您只需从
    serverTiming
    数据中提取密钥、IV 和标签长度,使用 PHP 插入要查找的名称(
    $rndStrIV
    $rndStr1
    2
    、...)。
  8. Base64-解码并重新组合Key,base64-解码IV,并将Tag Length乘以8(PHP使用字节,JS使用位)。
  9. $aesData
    回显到脚本中作为某处的变量。
    注意: 如果您尝试隐藏的 API 密钥(或任何数据)足够短,您可以使用与 AES 密钥相同的方法将此数据分割成不长于 IV 的块并将其一起发送也在
    Server-Timing
    中。不过,请记住您的网络服务器和目标浏览器的标头长度限制。
  10. 最后,使用 Subtle Crypto 导入密钥并解密数据。

理由:

  • 通过标头传递这些部分中的至少一个,可以在粗略浏览代码时“隐藏”它们。
  • 使用随机生成的零件名称会阻碍快速提取数据。
  • 打乱标头中的顺序有助于使正则表达式匹配变得不那么容易。
  • 将 AES 密钥分块可以防止使用长度匹配轻松确定哪些值是哪些值。

总的来说,这应该会使抓取过程变得更加复杂和复杂。

为什么这不安全:

  • AES Key、IV、Tag 和加密数据都在同一个 HTTP 响应中发送。加密背后不存在实际的安全性。使用 AES-GCM 加密数据只不过是混淆,将 API 密钥分成几个混乱的部分。
  • 即使变量名称是随机生成的,它们仍然在脚本中使用。聪明的抓取工具所要做的就是在 JavaScript 代码中查找变量名称,以确定哪个是 IV,以及其他变量的顺序。

样品:

<?php
 $addr = 'https://my.public.svc/tool/';
 $secret = 'ACCESS_TOKEN';
 $alg = 'aes-256-gcm';
 $jalg = 'AES-GCM';

 if (substr($_SERVER['HTTP_REFERER'], 0, strlen($addr)) !== $addr)
  die('window.close();');
 if ($_SERVER['HTTP_SEC_FETCH_DEST'] !== 'script')
  die('window.close();');
 if ($_SERVER['HTTP_SEC_FETCH_MODE'] !== 'no-cors')
  die('window.close();');
 if ($_SERVER['HTTP_SEC_FETCH_SITE'] !== 'same-origin')
  die('window.close();');

 function rndChrs() {
  $l = random_int(12, 16);
  $r = '';
  for ($i = 0; $i < $l; $i++) {
   $r.= chr(random_int(0x61, 0x7A));
  }
  return $r;
 }

 function splitKey($d, $l) {
  $c = ceil(strlen($d) / $l);
  $n = array();
  $t = array();
  for ($i = 0; $i < $c; $i++) {
   $k = rndChrs();
   $n[] = $k;
   $v = substr($d, $i * $l, $l);
   $t[] = $k.';desc="'.base64_encode($v).'"';
  }
  return [$n, $t];
 }

 function makeTime($t, $l) {
  $w = random_int(0, count($t) - 1);
  shuffle($t);
  $r = '';
  for ($i = 0; $i < count($t); $i++) {
   if ($r !== '')
    $r.= ',';
   $r.=$t[$i];
   if ($i === $w)
    $r.= ';dur='.$l;
  }
  return $r;
 }

 function makeList($rK, $rIV) {
  $m = array_merge($rK, array($rIV, 'tag'));
  shuffle($m);
  return '!t.'.implode(' || !t.', $m); 
 }

 header('Cache-Control: must-revalidate');
 header('Access-Control-Allow-Origin: '.$addr);
 header('Content-Type: text/javascript');

 $lKey = openssl_cipher_key_length($alg);
 $lIV = openssl_cipher_iv_length($alg);

 $aesKey = openssl_random_pseudo_bytes($lKey);
 $aesIV = openssl_random_pseudo_bytes($lIV);
 $aesTag = null;
 $encData = openssl_encrypt($secret, $alg, $aesKey, OPENSSL_RAW_DATA, $aesIV, $aesTag);
 $aesOut = base64_encode($encData.$aesTag);

 list($rndStrs, $aTime) = splitKey($aesKey, $lIV);
 $rndStrIV = rndChrs();
 $aTime[] = $rndStrIV.';desc="'.base64_encode($aesIV).'"';

 $timing = makeTime($aTime, strlen($aesTag));
 header('Server-Timing: '.$timing);
?>// <script>
var jSecret;

function _b(d) {
 let s = '';
 if (typeof d === 'string') {
  s = window.atob(d);
 } else {
  for (let i = 0; i < d.length; i++) {
   s += window.atob(d[i]);
  }
 }
 const l = s.length,
       r = new Uint8Array(l);
 for(let i = 0; i < l; i++) {
  r[i] = s.charCodeAt(i);
 }
 return r.buffer;
}

function _t() {
 const r = {};
 for (const f of ['navigation', 'resource']) {
  for (const {name: u, serverTiming: t} of performance.getEntriesByType(f)) {
   if (!t)
    continue;
   for (const {name: n, description: c, duration: l} of t) {
    r[n] = c;
    if (l !== 0)
     r['tag'] = l;
   }
  }
 }
 if (Object.keys(r).length < 1)
  return false;
 return r;
}

async function _d(e) {
 const a = '<?php echo $jalg; ?>';
 const t = _t();
 if (!t) {
  return;
 }
 if (<?php echo makeList($rndStrs, $rndStrIV) ?>) {
  return;
 }
 try {
  const k = await window.crypto.subtle.importKey('raw', _b([t.<?php echo implode(', t.', $rndStrs); ?>]), a, false, ['decrypt']);
  const r = await window.crypto.subtle.decrypt({name: a, iv: _b(t.<?php echo $rndStrIV; ?>), tagLength: t.tag * 8}, k, _b(e));
  jSecret = new TextDecoder().decode(r);
  console.log(jSecret);
 }
 catch(e) {
  return;
 }
}

_d('<?php echo $aesOut; ?>');

请注意,使用 Subtle Crypto 时,该过程变为异步,因此在使用输出值 (

jSecret
) 之前应检查其值。在另一个异步函数中等待函数 (
_d()
)(或使用
.then()
)将保证一个值。

© www.soinside.com 2019 - 2024. All rights reserved.