"Malformed UTF-8 characters" 尝试 json_encode 和包含一些加密值的数组时出错

"Malformed UTF-8 characters" error when trying to json_encode and array that holds some encrypted values

我需要将一些 JSON 数据发送到 API 端点,该端点要求对部分请求进行加密。 API 提供商提供给我的 public 密钥。这是相关的代码片段:

$key = "-----BEGIN PUBLIC KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END PUBLIC KEY-----";

openssl_public_encrypt('my_username', $username, openssl_pkey_get_public($key));
openssl_public_encrypt('my_pa55w0rd', $password, openssl_pkey_get_public($key));

$client = new GuzzleHttp\Client();
$result = $client->post(
    'https://api.domain.com/endpoint',
    [
        'headers' => [
            'Authorization' => 'Bearer ' . $bearerToken,
            'Content-Type' => 'application/json',
        ],
        'json' => [
            'username' => $username,
            'pasword' => $password,
            'unencrypted_key' => 'an unencrypted value,
        ]
    ]
);

上面的代码片段给我一个 json_encode error: Malformed UTF-8 characters, possibly incorrectly encoded 错误。在执行 echo $username; 时,我看到输出的字符串有一堆格式错误的字符:

我不确定我做错了什么,或者我是否应该采取其他方法在对值进行 json 编码之前对其进行加密。

注意: 虽然我没有在上面的代码中使用 json_encode 函数,但我相信 guzzle HTTP 库 json_encode 在发送之前是数组发出请求。

正如 msg 在评论中指出的那样:

openssl_public_encrypt returns a binary stream, that's the mangled output, you would need to encode it in ascii first, probably with base64_encode but why do you need encryption if the transport is already using ssl ? Check with the api provider.

但是,他们也有可能期待 hex-encoded 数据。请改用 bin2hex() 试试看。

如果他们有一些其他奇怪的格式(Base32Hex?),请随意使用 this RFC 4648 library 对您的消息进行编码。


重要安全信息

这不是您问题的答案,但您和您的 API 都应该知道这一点。

openssl_public_encrypt 默认为带有 PKCS#1 v1.5 填充的 RSA,易受 a padding oracle attack.

攻击

至少:与您通信的 API 的开发人员应该停止接受使用 PKCS#1v1.5 填充的 RSA 加密的数据,而只接受 OAEP 密文。

ways to work around Bleichenbacher's 1998 attack,但它们很乱,只能在协议级别解决,不能在库或原始级别解决。

但是RSA加密比较乱还有其他原因:You can't encrypt large messages directly with RSA.

还有plenty of other ways RSA can go wrong.

推荐

更好的解决方案是完全停止使用 RSA。

锂钠 has bindings in most popular programming languages (and is included in the PHP 7.2 core) and makes this easier to get right.

// On your end...
$sendToProvider = sodium_bin2hex(
    sodium_crypto_box_seal($privateData, $publicKey)
);

// On their end...
$decrypted = sodium_crypto_box_seal_open(
    sodium_hex2bin($encrypted),
    $keypair
);

无论哪种方式,您都会遇到编码问题(因为 API 和 return 原始二进制字符串)。但是,使用 libsodium 会带来 side-stepped 大量的安全问题,在您阅读 Whosebug 的回答之前,您可能并不知道这些问题。

另外:如果你不知道,不要难过。你不是一个人。甚至 Zend Framework 的密码学库 got bit by these RSA vulnerabilities,他们的团队中也有 Enrico Zimuel。 RSA 只是现实世界密码学的一个糟糕选择。