如何配置Android RSA密钥生成(或密钥使用),使其像Delphi TurboPower Lockbox 2 RS



我有以下代码用于Delphi 10.2 TurboPower LockBox 2 RSA密钥生成及其作为某些字符串的重新定义:

//Object properties 
object LbRSA1024: TLbRSA
PrimeTestIterations = 20
KeySize = aks1024
Left = 416
Top = 248
end
//Key generation - so simple!
LbRSA1024.GenerateKeyPair;
//Getting generated key as string 
function TMainForm.GetPublicKey1024AsString: string;
var
str1, str2: TStringStream;
begin
Result:='';
if (LbRSA1024.PublicKey.Exponent.Int.dwUsed = 0)
or (LbRSA1024.PublicKey.Modulus.Int.dwUsed = 0) then
exit;
str1:= TStringStream.Create('');
str2:= TStringStream.Create('');
try
LbRSA1024.PublicKey.StoreToStream(str1);
str1.Position:=0;
//LbEncodeBase64(str1,str2);
TLbBase64.LbEncodeBase64(str1,str2);
Result:=str2.DataString;
finally
str1.Free;
str2.Free;
end;
end;

我得到了~200个字符的公钥字符串(例如...dh2dMTy/ab...)我将他的字符串分配给 Android kotlin 变量 publicKeyString,并尝试使用从 Delphi 生成的这个公钥加密其他一些字符串。Android Kotlin 代码是:

val publicKeyBytes: ByteArray = Base64.decode(publicKeyString, Base64.DEFAULT)
val X509PublicKey: X509EncodedKeySpec = X509EncodedKeySpec(publicKeyBytes)
val kf: KeyFactory = KeyFactory.getInstance("RSA")
val publicKey: PublicKey = kf.generatePublic(X509PublicKey)
val cipher: Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val bytes = cipher.doFinal(s.toByteArray())
val result: String = String(bytes, Charsets.UTF_8)

通常它不起作用 - 不接受密钥并且错误消息是(我尝试了上述代码的一些变体):

Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic (:-1)
...
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic (:-1) 
at java.security.KeyFactory.generatePublic (:-1) 

因此,Delphi LockBox和Android/javax RSA可能具有不同的密钥格式。所以 - 我试图做两件事。首先 - 我检查了 Delphi 密钥生成的代码 - 特别是 - LbRsa.pasclass procedure TRSA.GenerateRSAKeysEx(var PrivateKey, PublicKey : TLbRSAKey; KeySize : TLbAsymKeySize; PrimeTestIterations : Byte; Callback : TLbRSACallback);但是这段代码是完全通用的 - 大整数被生成并存储为成员变量,然后使用我上面已经提供的代码流式传输到字符串。

然后我尝试在Android/javax中生成RSA密钥,并检查它们是否与LockBox生成的密钥相同。我使用了以下代码:

val REG_KEY: String = "REG_KEY"
val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA /*, ANDROID_KEYSTORE */)
val builder = KeyGenParameterSpec.Builder(REG_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setKeySize(1024)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
//.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
generator.initialize(builder.build())
val keys = generator.generateKeyPair()
Log.i( "Cryptic", keys.private.toString())
Log.i( "Cryptic", keys.public.toString())
Log.i( "Cryptic", keys.private.encoded.contentToString())
Log.i( "Cryptic", keys.public.encoded.contentToString())
Log.i( "Cryptic", keys.private.encoded.toString(Charsets.UTF_8))
Log.i( "Cryptic", keys.public.encoded.toString(Charsets.UTF_8))

我遇到错误消息,因为私钥为空,但生成公钥:

java.lang.NullPointerException: keys.private.encoded must not be null
at com.batsoft.stockmobile.service.Cryptography.encryptString(Cryptography.kt:35)

我仍在寻求将其转换为字符串,使其看起来像 LockBox 生成的密钥 - 只是为了比较。

但是在现阶段我已经很困惑了 - 为什么我必须为密钥生成提供链接模式和填充方案?我的理解是密钥只是编码的大整数。而链接模式、填充方案仅用于加密/解密?当然,我需要提供密钥大小,这是可以理解的。

所以 - 我的目标是配置 Android/javax RSA 密钥生成和 RSA 密钥使用,使其完全符合 RSA 密钥生成和在 Delphi 10.2 LockBox 2 中的使用。我的目标是在Android程序中使用LockBox生成的密钥。我已经描述了我已经采用的路径数量,但我仍然没有设法生成与 Delphi 密钥相同格式的 javax 密钥。由于我不知道密码箱中密钥的确切配置(我猜 - 除了密钥大小之外没有),我也无法在Android/javax上配置我的加密/解密。

如何通过更改Android/javax代码来实现这种一致性?

附加信息:Delphi LbAsym.pasprocedure TLbAsymmetricKey.StoreToStream(aStream : TStream);非常重要,因为它将键整数保存到流中。在不使用密码的情况下,代码非常紧张:

aStream.Write(KeyBuf, Len);

Pascal 代码中的注释如下:

save key to ASN.1 format stream (encrypt if necessary)

所以,也许我的问题被重新表述 - 如何以ASN.1格式保存Android/javax生成的密钥或从ASN.1格式读取密钥?

附加信息 2:我正在寻找将 Java 生成的密钥保存为 PEM 字符串的方法,这可以通过 Android Kotlin 代码来完成:

val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA /*, ANDROID_KEYSTORE */)
val builder = KeyGenParameterSpec.Builder(REG_KEY,
KeyProperties.PURPOSE_ENCRYPT and KeyProperties.PURPOSE_DECRYPT)
.setKeySize(1024)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
//.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
generator.initialize(builder.build())
val keys = generator.generateKeyPair()
//https://stackoverflow.com/questions/25129822/export-rsa-public-key-to-pem-string-using-java
val writer = StringWriter()
val pemWriter = PemWriter(writer)
pemWriter.writeObject(PemObject("PUBLIC KEY", keys.public.encoded))
pemWriter.flush()
pemWriter.close()
Log.i( "Cryptic", writer.toString())

请注意,这需要添加 gradle 依赖项:

implementation 'org.bouncycastle:bcpkix-jdk15to18:1.68'
implementation 'org.bouncycastle:bcprov-jdk15to18:1.68'

为了使用Delphi生成的密钥(显然LockBox 2生成并保存为PEM字符串),我需要解析PEM字符串并将它们分配为javax Cipher密钥。

附加信息3:这很奇怪。Java 生成的字符串(来自附加信息 2)是一个大约 25 个字符长的 gan Delphi 生成的(基于64 编码)字符串(当然,我已经删除了初始和尾随字符串,其中一些带有 PEM 文件,只是像"===PUBLIC KEY==="这样的常量),它可以在我的初始代码中完美地用作公钥字符串 - 使用这种公钥进行加密非常有效。Delphi 公钥字符串短了大约 25 个字符,不起作用。

所以 - 我已经弄清楚每个步骤内部发生了什么,并试图建立平行的步骤,但最终我未能找到解决方案。

其他信息 4:以下是 base64 编码的公钥:

生成自德尔福:

MIGIAoGBALtEMVXxHBWzBx/AzO/aOHrYEQZB3VlqYBvqX/SHES7ehERXaCbUO5aEwyZcDrdh2dMTy/abNDaFJK4bEqghpC6yvCNvnTqjAz+bsD9UqS0w5CUh3KHwqhPv+HFGcF7rAuU9uoJcWXbTC9tUBEG7rdmdmMatIgL1Y4ebOACQHn1xAgIlKg==

从 Android Kotlin/java 生成:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuQi7gMZwWL1iEhNVgdu23S/rYYhtntXQlfVVBjcGiSE8EXzjjnZHxcYHcIszV0F6F20msGK8MFernJpWg8k7J3GLH4TYkQwEEy6jWnRdEB3uqQWFCNQ/CflCHtq1o1iSS0qmXcHQuI7zZ0cHd5FNDg4Bl/DveftEje9yTgUXN3wIDAQAB

我不确定,但也许有一些在线服务可以 base64 解码这些字符串,然后根据某种方案从中提取大整数并检测格式。

如注释中所述,Delphi 代码生成 PKCS#1 格式的公钥,而 Kotlin 代码期望 X.509/SPKI 格式的密钥.
使用 BouncyCastle,Kotlin 代码可以导入 PKCS#1 公钥。 这需要类PEMParserJcaPEMKeyConverter

例:

import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import javax.crypto.Cipher
import java.security.PublicKey
import java.io.FileReader
import java.util.Base64
...
val inputFile: String = "<path to PKCS#1 PEM file>"
// Key import
var publicKey: PublicKey? = null
FileReader(inputFile).use { fileReader ->
PEMParser(fileReader).use { pemParser ->
val spki: SubjectPublicKeyInfo = pemParser.readObject() as SubjectPublicKeyInfo
val converter = JcaPEMKeyConverter()
converter.setProvider(BouncyCastleProvider())
publicKey = converter.getPublicKey(spki) 
}
}
// Encryption
val cipher: Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val ciphertext: ByteArray = cipher.doFinal("The quick brown fox jumps over the lazy dog".toByteArray())
val ciphertextB64: String = Base64.getEncoder().encodeToString(ciphertext);
println(ciphertextB64)

该代码导入 PKCS#1 格式的 PEM 编码公钥。PEM 编码包括特定于格式的页眉和页脚,在正文中,每 64 个字符后有换行符。PEM 编码的德尔菲密钥为:

-----BEGIN RSA PUBLIC KEY-----
MIGIAoGBALtEMVXxHBWzBx/AzO/aOHrYEQZB3VlqYBvqX/SHES7ehERXaCbUO5aE
wyZcDrdh2dMTy/abNDaFJK4bEqghpC6yvCNvnTqjAz+bsD9UqS0w5CUh3KHwqhPv
+HFGcF7rAuU9uoJcWXbTC9tUBEG7rdmdmMatIgL1Y4ebOACQHn1xAgIlKg==
-----END RSA PUBLIC KEY-----

很抱歉,导入德尔福密钥不起作用。显示依赖于提供程序的错误消息,例如在BouncyCastleProvider的情况下:

PEMException: unable to convert key pair: encoded key spec not recognized: RSA publicExponent is even

事实上,德尔菲代码生成的密钥有一个值为 9514 (0x252A) 的偶数公共指数:

0:d=0  hl=3 l= 136 cons: SEQUENCE
3:d=1  hl=3 l= 129 prim: INTEGER  :BB443155F11C15B3071FC0CCEFDA387AD8110641DD596A601BEA5FF487112EDE8444576826D43B9684C3265C0EB761D9D313CBF69B34368524AE1B12A821A42EB2BC236F9D3AA3033F9BB03F54A92D30E42521DCA1F0AA13EFF87146705EEB02E53DBA825C5976D30BDB540441BBADD99D98C6AD2202F563879B3800901E7D71
135:d=1  hl=2 l=   2 prim: INTEGER  :252A

这不应该是这种情况(φ(n)或λ(n)和e则不是互质),s.在这里。因此,您应该检查德尔菲代码中的密钥生成。


另一个问题在行中:

val result: String = String(bytes, Charsets.UTF_8)

密文(通常包含不符合 UTF-8 标准的字节序列)的 UTF-8 解码会损坏此密文。
如果要将密文转换为字符串,则必须应用二进制到文本编码,例如 Base64。

最新更新