如何配置Android RSA密钥生成(或密钥使用),使其工作起来像Delphi TurboPower Lockbox 2 RSA密钥生成/使用?

pdsfdshx  于 2022-10-16  发布在  Android
关注(0)|答案(1)|浏览(185)

我有以下代码,用于生成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变量Public KeyString,并尝试使用从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密钥生成的代码--Speciicaly-LbRsa.pas class procedure TRSA.GenerateRSAKeysEx(var PrivateKey, PublicKey : TLbRSAKey; KeySize : TLbAsymKeySize; PrimeTestIterations : Byte; Callback : TLbRSACallback);,但这段代码完全是泛型的--大整数被生成并存储为成员变量,然后用我在上面提供的代码流到字符串中。
然后我尝试在Android/javax中生成RSA密钥,并检查它们是否与密码箱生成的密钥相同。我使用了以下代码:

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)

我仍然在寻求将其转换为字符串,以看起来像锁箱生成的钥匙-只是为了比较。
但在现阶段,我已经感到困惑--为什么我要为密钥生成提供链接模式和填充方案?我的理解是,键只是编码的大整数。和链接模式,填充方案仅用于加密/解密?当然,我需要提供密钥大小,这是可以理解的。
所以-我的目标是配置Android/javax RSA密钥生成和RSA密钥使用,使其完全符合在Delphi 10.2 Lockbox 2中生成和使用的RSA密钥。我的目标是在Android程序中使用Lockbox生成的密钥。我已经描述了我已经采用的路径的数量,但我仍然没有设法以与Delphi键相同的格式生成javax键。由于我不知道密码箱中密钥的确切配置(我猜--除了密钥大小,没有其他密钥),我也无法在Android/javax上配置我的加密/解密。
如何通过更改Android/javax代码来实现这种整合?

附加信息:Delphi LbAsym.pas procedure TLbAsymmetricKey.StoreToStream(aStream : TStream);非常重要,因为它将key-整数保存到流中。在不使用密码短语的情况下,代码非常简单:

aStream.Write(KeyBuf, Len);

PASCAL代码中的注解如下所示:

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

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

附加信息2:我正在寻找将Java生成的key保存为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 KeyString-使用这样的公钥加密工作得很好。Delphi公钥字符串大约短了25个字符,不起作用。

所以-我已经弄清楚了每一步的内部情况,并试图建立并行的步骤,但最终我找不到解决方案。

附加信息4:这里是Base64编码的公钥:

由Delphi生成:

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

Android Kotlin/Java生成:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuQi7gMZwWL1iEhNVgdu23S/rYYhtntXQlfVVBjcGiSE8EXzjjnZHxcYHcIszV0F6F20msGK8MFernJpWg8k7J3GLH4TYkQwEEy6jWnRdEB3uqQWFCNQ/CflCHtq1o1iSS0qmXcHQuI7zZ0cHd5FNDg4Bl/DveftEje9yTgUXN3wIDAQAB

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

9udxz4iz

9udxz4iz1#

如注解中所述,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编码的Delphi密钥为:

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

不幸的是,您的Delphi密钥的导入不起作用。会显示一条与提供程序相关的错误消息,例如BouncyCastleProvider:

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

实际上,Delphi代码生成的密钥具有一个偶数公共指数,其值为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。因此,您应该检查Delphi代码中的密钥生成。
另一个问题是:

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

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

相关问题