看起来好像有以下场景:
- 32 字节对称密钥,在服务器端以 base64 格式生成
- 这个 32 字节的密钥在服务器端使用来自客户端的公钥加密
- 在客户端解密需要使用客户端的私钥,它似乎是用 PKCS8 格式编码的 base64
在客户端我们可以:
- 使用密钥构造一个 EncryptedMessage
- 创建 PrivateKey 实例
- 然后用私钥解密消息
- 然后将结果拆分为两个数据实例,每个实例 16 个字节,如问题示例所示
此函数的源代码如下所示:
static func decodeTapoKey(key: String, keyPair: KspKeyPair) throws -> (Data, Data) {
let keyWithoutWhiteSpaces = String(key.compactMap { $0.isWhitespace ? nil : $0 })
let encrypted = try EncryptedMessage(base64Encoded: keyWithoutWhiteSpaces)
let privateKey = try PrivateKey(base64Encoded: keyPair.privateKey)
let decryptedKey = try encrypted.decrypted(with: privateKey, padding: SecPadding.PKCS1)
let b_arr = decryptedKey.data[0..<16]
let b_arr2 = decryptedKey.data[16..<32]
return (b_arr, b_arr2)
}
测试
可以生成私有和公共测试密钥以及 256 位(= 32 字节)的 AES 密钥。有了这些数据,我们应该得到与 Python 脚本中相同的密钥二进制输出。
对于十六进制数据的输出,我们可以使用这个很好的答案中的函数hexDescription:https://stackoverflow.com/a/39075044。
一个完全独立的例子:
Decrypt.swift
import Foundation
import SwiftyRSA
struct KspKeyPair {
let privateKey: String
let publicKey: String
}
final class Decrypt {
static func decodeTapoKey(key: String, keyPair: KspKeyPair) throws -> (Data, Data) {
let keyWithoutWhiteSpaces = String(key.compactMap { $0.isWhitespace ? nil : $0 })
let encrypted = try EncryptedMessage(base64Encoded: keyWithoutWhiteSpaces)
let privateKey = try PrivateKey(base64Encoded: keyPair.privateKey)
let decryptedKey = try encrypted.decrypted(with: privateKey, padding: SecPadding.PKCS1)
let b_arr = decryptedKey.data[0..<16]
let b_arr2 = decryptedKey.data[16..<32]
return (b_arr, b_arr2)
}
}
extension Data {
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
let privateKey =
"""
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMY+gDb9hwm9gmYB+iDYxKPngQ3X
Rm3U4tzea9NLtmHgWipM44tE4zWkEeaBawWbFjrnT0lPriTXPn6OvvDRCY7eRejB9wP/aezoNMMr
A2XQ6Du8gcopM1ylUJCWjr9nftTb6wn75wY5IMPUKM2N0uZ+dvfQH4qEQwE05z6Iz1szAgMBAAEC
gYAW5UcHktZSwKlbwKSzwHVNfMJB5/gBXVHqMmH/oEHrIe8n7YNmJUmce1t55L6IgjXaDbbxf5tc
M+PK2A+jXnEc8+2snDbI1PpBepEwg5vzZ8RYnhGZT6P0/PsIqInkTIZnfXDnQ6wBapO1m9xJDe/B
fyWMVRa6JqJtgQ0XpfG76QJBAOgKPSF5wJKC69lCbvyh38fQoeJxEKrD03FGXGBDLLYOHqfijdMT
vcnE415994MI3fTy2dWm/yB8wZ7DvYVwVX8CQQDatuZyi6LhLhU47l5vpFsOnRWGhGrZaIN0o7N/
1v5Vwieujrwy/yW2uCvUeXVnmohJa+sFSr29HO4PEQwWtFxNAkBXSxrKUDJ5K9Wsa0izs/YrBrsQ
JDb/9yHBmJXCBSN57f/sateuE9wvXummr78AxcIyl3YJ4YRTZXu1za+r1qHjAkEAzjkavPKQ18W9
2PpZLOdJvFO9EiMVJH15Rad8/pNXKMFy7RJEvcj6ZHjvSt5jJxb8Xk5VQZ4hnYkDpk0qmtXhGQJB
AIO6TEXkHU3Sn0qTZsPmpu+EfCADLNKG43kiv74+cRzS7Th2A6E1yq5Y/lmbdpYaHqm0mKgvMHb2
ls7DtRTYoXo=
"""
let publicKey =
"""
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGPoA2/YcJvYJmAfog2MSj54EN10Zt1OLc3mvT
S7Zh4FoqTOOLROM1pBHmgWsFmxY6509JT64k1z5+jr7w0QmO3kXowfcD/2ns6DTDKwNl0Og7vIHK
KTNcpVCQlo6/Z37U2+sJ++cGOSDD1CjNjdLmfnb30B+KhEMBNOc+iM9bMwIDAQAB
"""
let secretKey =
"""
YyqsaPrgmVKcjS6UQY5aV0KtOdovqbuwGQMkZMoHtwygZQVy3fiLp0/03B4LKkYtIm0SBPYJw/cV
w2DyZYsPPvX71RbuU9Pp+AJkWTfReEWhSW2vonWv6HIULK94hREHt7S51oBNXo5QP4wn2F4PD3hg
P2DYBAw7guMy9wbGB+4=
"""
override func viewDidLoad() {
super.viewDidLoad()
let keyPair = KspKeyPair(privateKey: privateKey, publicKey: publicKey)
do {
let (b_arr, b_arr2) = try Decrypt.decodeTapoKey(key: secretKey, keyPair: keyPair)
print(b_arr.hexDescription)
print(b_arr2.hexDescription)
} catch {
print("decoding failed: \(error)")
}
}
}
输出是:
4907d77a736619e6338f7e88fbed565f
40decbd392295df33afa817d23ac2824
Python
基于您的 Python 脚本也是一个完全独立的示例:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
import logging
from collections import namedtuple
KspKeyPair = namedtuple('KspKeyPair', ['private_key', 'public_key'])
logger = logging.getLogger('root')
def decode_handshake_key(key: str, keyPair: KspKeyPair):
logger.debug(f"Will decode handshake key (...{key[5:]}) using current key pair")
decode: bytes = base64.b64decode(key.encode("UTF-8"))
decode2: bytes = base64.b64decode(keyPair.private_key)
cipher = PKCS1_v1_5.new(RSA.import_key(decode2))
do_final = cipher.decrypt(decode, None)
if do_final is None:
raise ValueError("Decryption failed!")
b_arr: bytearray = bytearray()
b_arr2: bytearray = bytearray()
for i in range(0, 16):
b_arr.insert(i, do_final[i])
for i in range(0, 16):
b_arr2.insert(i, do_final[i + 16])
print(b_arr.hex())
print(b_arr2.hex())
if __name__ == "__main__":
privateKey = """
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMY+gDb9hwm9gmYB+iDYxKPngQ3X
Rm3U4tzea9NLtmHgWipM44tE4zWkEeaBawWbFjrnT0lPriTXPn6OvvDRCY7eRejB9wP/aezoNMMr
A2XQ6Du8gcopM1ylUJCWjr9nftTb6wn75wY5IMPUKM2N0uZ+dvfQH4qEQwE05z6Iz1szAgMBAAEC
gYAW5UcHktZSwKlbwKSzwHVNfMJB5/gBXVHqMmH/oEHrIe8n7YNmJUmce1t55L6IgjXaDbbxf5tc
M+PK2A+jXnEc8+2snDbI1PpBepEwg5vzZ8RYnhGZT6P0/PsIqInkTIZnfXDnQ6wBapO1m9xJDe/B
fyWMVRa6JqJtgQ0XpfG76QJBAOgKPSF5wJKC69lCbvyh38fQoeJxEKrD03FGXGBDLLYOHqfijdMT
vcnE415994MI3fTy2dWm/yB8wZ7DvYVwVX8CQQDatuZyi6LhLhU47l5vpFsOnRWGhGrZaIN0o7N/
1v5Vwieujrwy/yW2uCvUeXVnmohJa+sFSr29HO4PEQwWtFxNAkBXSxrKUDJ5K9Wsa0izs/YrBrsQ
JDb/9yHBmJXCBSN57f/sateuE9wvXummr78AxcIyl3YJ4YRTZXu1za+r1qHjAkEAzjkavPKQ18W9
2PpZLOdJvFO9EiMVJH15Rad8/pNXKMFy7RJEvcj6ZHjvSt5jJxb8Xk5VQZ4hnYkDpk0qmtXhGQJB
AIO6TEXkHU3Sn0qTZsPmpu+EfCADLNKG43kiv74+cRzS7Th2A6E1yq5Y/lmbdpYaHqm0mKgvMHb2
ls7DtRTYoXo=
"""
publicKey = """
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGPoA2/YcJvYJmAfog2MSj54EN10Zt1OLc3mvT
S7Zh4FoqTOOLROM1pBHmgWsFmxY6509JT64k1z5+jr7w0QmO3kXowfcD/2ns6DTDKwNl0Og7vIHK
KTNcpVCQlo6/Z37U2+sJ++cGOSDD1CjNjdLmfnb30B+KhEMBNOc+iM9bMwIDAQAB
"""
secretKey = """
YyqsaPrgmVKcjS6UQY5aV0KtOdovqbuwGQMkZMoHtwygZQVy3fiLp0/03B4LKkYtIm0SBPYJw/cV
w2DyZYsPPvX71RbuU9Pp+AJkWTfReEWhSW2vonWv6HIULK94hREHt7S51oBNXo5QP4wn2F4PD3hg
P2DYBAw7guMy9wbGB+4=
"""
keyPair = KspKeyPair(privateKey, publicKey)
decode_handshake_key(secretKey, keyPair)
而且这个 Python 程序的输出和 iOS Swift 程序的输出完全一样。所以 Swift 部分似乎工作正常。
Java
对于 Java 例程,也应该给出相同的输出。让我们用这个独立的 Java 示例来测试它:
package com.software7;
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class Main {
static String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMY+gDb9hwm9gmYB+iDYxKPngQ3XRm3U4tzea9NLtmHgWipM44tE4zWkEeaBawWbFjrnT0lPriTXPn6OvvDRCY7eRejB9wP/aezoNMMrA2XQ6Du8gcopM1ylUJCWjr9nftTb6wn75wY5IMPUKM2N0uZ+dvfQH4qEQwE05z6Iz1szAgMBAAECgYAW5UcHktZSwKlbwKSzwHVNfMJB5/gBXVHqMmH/oEHrIe8n7YNmJUmce1t55L6IgjXaDbbxf5tcM+PK2A+jXnEc8+2snDbI1PpBepEwg5vzZ8RYnhGZT6P0/PsIqInkTIZnfXDnQ6wBapO1m9xJDe/BfyWMVRa6JqJtgQ0XpfG76QJBAOgKPSF5wJKC69lCbvyh38fQoeJxEKrD03FGXGBDLLYOHqfijdMTvcnE415994MI3fTy2dWm/yB8wZ7DvYVwVX8CQQDatuZyi6LhLhU47l5vpFsOnRWGhGrZaIN0o7N/1v5Vwieujrwy/yW2uCvUeXVnmohJa+sFSr29HO4PEQwWtFxNAkBXSxrKUDJ5K9Wsa0izs/YrBrsQJDb/9yHBmJXCBSN57f/sateuE9wvXummr78AxcIyl3YJ4YRTZXu1za+r1qHjAkEAzjkavPKQ18W92PpZLOdJvFO9EiMVJH15Rad8/pNXKMFy7RJEvcj6ZHjvSt5jJxb8Xk5VQZ4hnYkDpk0qmtXhGQJBAIO6TEXkHU3Sn0qTZsPmpu+EfCADLNKG43kiv74+cRzS7Th2A6E1yq5Y/lmbdpYaHqm0mKgvMHb2ls7DtRTYoXo=";
static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGPoA2/YcJvYJmAfog2MSj54EN10Zt1OLc3mvTS7Zh4FoqTOOLROM1pBHmgWsFmxY6509JT64k1z5+jr7w0QmO3kXowfcD/2ns6DTDKwNl0Og7vIHKKTNcpVCQlo6/Z37U2+sJ++cGOSDD1CjNjdLmfnb30B+KhEMBNOc+iM9bMwIDAQAB";
static String secretKey = "YyqsaPrgmVKcjS6UQY5aV0KtOdovqbuwGQMkZMoHtwygZQVy3fiLp0/03B4LKkYtIm0SBPYJw/cVw2DyZYsPPvX71RbuU9Pp+AJkWTfReEWhSW2vonWv6HIULK94hREHt7S51oBNXo5QP4wn2F4PD3hgP2DYBAw7guMy9wbGB+4=";
public static void main(String[] args) {
KspKeyPair keyPair = new KspKeyPair(privateKey, publicKey);
C658a result = decodeTapoKey(secretKey, keyPair);
System.out.println(Hex.encodeHexString(result.bArr));
System.out.println(Hex.encodeHexString(result.bArr2));
}
public static C658a decodeTapoKey(String key, KspKeyPair keyPair) {
try {
byte[] decode = KspB64.decode(key.getBytes("UTF-8"));
byte[] decode2 = KspB64.decode(keyPair.getPrivateKey());
Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey p = kf.generatePrivate(new PKCS8EncodedKeySpec(decode2));
instance.init(Cipher.DECRYPT_MODE, p);
byte[] doFinal = instance.doFinal(decode);
byte[] bArr = new byte[16];
byte[] bArr2 = new byte[16];
System.arraycopy(doFinal, 0, bArr, 0, 16);
System.arraycopy(doFinal, 16, bArr2, 0, 16);
return new C658a(bArr, bArr2);
} catch (Exception ex) {
System.out.println("Something went wrong: " + ex.getMessage());
return null;
}
}
}
class C658a {
byte[] bArr;
byte[] bArr2;
public C658a(byte[] bArr, byte[] bArr2) {
this.bArr = bArr;
this.bArr2 = bArr2;
}
}
class KspKeyPair {
String privateKey;
String publicKey;
public KspKeyPair(String privateKey, String publicKey) {
this.privateKey = privateKey;
this.publicKey = publicKey;
}
public byte[] getPrivateKey() { return privateKey.getBytes(); }
public byte[] getPublicKey() { return publicKey.getBytes(); }
}
class KspB64 {
public static byte[] decode(byte[] bytes) {
return Base64.getMimeDecoder().decode(bytes);
}
}
事实上,Java 程序提供了相同的结果。