Not getting the right result in base64 string when I try to convert base64 encoded string EC Public Key generated on iOS to Java PublicKey

huangapple go评论70阅读模式
英文:

Not getting the right result in base64 string when I try to convert base64 encoded string EC Public Key generated on iOS to Java PublicKey

问题

根据你提供的代码和描述,你的目标是将来自iOS设备的Base64编码的公钥字符串转换为Java中的公钥,以便用于计算两方之间的共享密钥。你在Android环境中使用了BouncyCastle库来执行这个操作。

然而,在你的代码中,你可能会遇到一些问题。首先,你的输入和输出Base64字符串不匹配,这表明在编码和解码公钥时可能存在问题。你可以尝试以下几种方法来解决这个问题:

  1. 确保输入的Base64字符串没有额外的空格或换行符。 在你的示例中,pkIOS的值被包围在"字符中,这可能是不必要的。确保pkIOS中只包含Base64编码的公钥。

  2. 检查编码和解码的方式。 你可以使用不同的方式来编码和解码Base64,例如Base64.encodeToString()Base64.decode()。确保在编码和解码时使用相同的方式和参数,以免出现不匹配的情况。

  3. 验证BouncyCastle库版本。 你已经提供了你使用的BouncyCastle版本,但要确保它与你的代码兼容。有时,不同版本的库可能会导致不一致的结果。如果可能的话,尝试更新到最新版本并查看是否有改进。

  4. 检查iOS设备上的公钥生成代码。 确保iOS设备上生成的Base64编码的公钥与你的预期相匹配。如果iOS端有问题,那么无论你如何处理,结果都会不一致。

  5. 调试输出。 你可以使用日志输出来调试代码,以查看生成的公钥是否与输入相匹配。这有助于确定问题出现在哪里。

最后,确保你理解公钥的格式和编码,以便更好地调试和解决问题。希望这些建议有助于解决你遇到的问题。如果问题仍然存在,请提供更多细节,以便获得更具体的帮助。

英文:

As the title suggests I am trying to convert base64 encoded string (EC Public Key) generated on IOS device(Swift) to Java PublicKey which will be used to calculate a Shared Secret Key between two parties. There is neither runtime nor compile time exception/error in the code, it compiles and runs successfully and generates a PublicKey but when I encode the PublicKey (Base64.encodeToString(PublicKey.encoded), Base64.NO_WRAP) back to Base64 string to confirm whether I have gotten the same public key I have passed as an argument, they are not the same.

import android.util.Base64
import org.bouncycastle.asn1.sec.SECNamedCurves
import org.bouncycastle.math.ec.ECCurve
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.security.*
import java.security.spec.*
import javax.crypto.*
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
    
    
fun iosB64EncodedStrPKToPK(iOSB64EncodedPK: String): PublicKey {
    val decodedPK = Base64.decode(iOSB64EncodedPK, Base64.NO_WRAP)
    val x9ECParamSpec = SECNamedCurves.getByName("secp256r1")
    val curve = x9ECParamSpec.curve
    val point = curve.decodePoint(decodedPK)
    val xBcEC = point.affineXCoord.toBigInteger()
    val yBcEC = point.affineYCoord.toBigInteger()
    val gBcEC = x9ECParamSpec.g
    val xGBcEC = gBcEC.affineXCoord.toBigInteger()
    val yGBcEC = gBcEC.affineYCoord.toBigInteger()
    val hBcEC = x9ECParamSpec.h.toInt()
    val nBcEC = x9ECParamSpec.n
    val jPEC = ECPoint(xBcEC, yBcEC)
    val gJpEC = ECPoint(xGBcEC, yGBcEC)
    val jEllipticCurve = convertECCurveToEllipticCurve(curve, gJpEC, nBcEC, hBcEC)
    val eCParameterSpec = ECParameterSpec(jEllipticCurve, gJpEC, nBcEC, hBcEC)
    val ecPubLicKeySpec = ECPublicKeySpec(jPEC, eCParameterSpec)
    val keyFactorySpec = KeyFactory.getInstance("EC")
    return keyFactorySpec.generatePublic(ecPubLicKeySpec)
}
    
private fun convertECCurveToEllipticCurve(
    curve: ECCurve,
    ecPoint: ECPoint,
    n: BigInteger,
    h: Int
): EllipticCurve {
    val ecField = ECFieldFp(curve.field.characteristic)
    val firstCoefficient = curve.a.toBigInteger()
    val secondCoefficient = curve.b.toBigInteger()
    val ecParams = ECParameterSpec(
        EllipticCurve(ecField, firstCoefficient, secondCoefficient),
            ecPoint,
            n,
            h
        )
    return ecParams.curve
}

The public key I am passing to the iosB64EncodedStrPKToPK() function: BAlWWu46il/ly6Axd/qclmhEVhGth93QN5+h3JBJEKEmhKd1LfqkpCqX1cT1cQDs9nPq9Lq0/FtZitkjr7Rqd94=

The output I get: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECVZa7jqKX+XLoDF3+pyWaERWEa2H3dA3n6HckEkQoSaEp3Ut+qSkKpfVxPVxAOz2c+r0urT8W1mK2SOvtGp33g==

val pkIOS = "BAlWWu46il/ly6Axd/qclmhEVhGth93QN5+h3JBJEKEmhKd1LfqkpCqX1cT1cQDs9nPq9Lq0/FtZitkjr7Rqd94="

Log.i("SOME_TAG","PUBLIC_KEY_IOS:${Base64.encodeToString(iosB64EncodedStrPKToPK(pkIOS).encoded,Base64.NO_WRAP)}") 

I am not an expert on the matter, maybe someone may guide me in the right direction and can see the mistake I am making. Cryptography is out of my field expertise.

I have tried Googling, ChatGPT and other insightful resources, if you know a source around the issue I would gladly accept it too.

I am running the code in an Android Environment

The version of BouncyCastle I am using:

def bouncy_castle_version = '1.70'
implementation "org.bouncycastle:bcpkix-jdk15on:$bouncy_castle_version"
implementation "org.bouncycastle:bcprov-jdk15on:$bouncy_castle_version"

答案1

得分: 0

以下是翻译好的部分:

  • The input BAlW... is an uncompressed public EC key (Base64 encoded).
  • The output MFkw... is an ASN.1/DER encoded key in X.509/SPKI format (Base64) encoded.

As can be seen, the ASN.1/DER encoded X.509/SPKI key contains the uncompressed public key at the end (the last 65 bytes).

Background:
Keep in mind that a public EC key is a point (x, y) on an EC curve (obtained by multiplying the private key by the generator point) and that there are different formats for its representation, e.g. the following two:

  • The uncompressed format, which corresponds to the concatenation of a 0x04 byte, and the x and y coordinates of the point: 04|x|y.
    For the secp256r1 curve (aka prime256v1 aka NIST P-256), x and y are both 32 bytes, so the uncompressed key is 65 bytes.
  • The X.509/SPKI format as defined in RFC 5280. This format is described with ASN.1 and serialized/encoded with DER (s. here).
英文:

The keys are identical, only their formats differ:

  • The input BAlW... is an uncompressed public EC key (Base64 encoded).
  • The output MFkw... is an ASN.1/DER encoded key in X.509/SPKI format (Base64) encoded.

This can be easily verified by encoding both keys not in Base64 but in hex:

input :                                                     0409565aee3a8a5fe5cba03177fa9c9668445611ad87ddd0379fa1dc904910a12684a7752dfaa4a42a97d5c4f57100ecf673eaf4bab4fc5b598ad923afb46a77de
output: 3059301306072a8648ce3d020106082a8648ce3d0301070342000409565aee3a8a5fe5cba03177fa9c9668445611ad87ddd0379fa1dc904910a12684a7752dfaa4a42a97d5c4f57100ecf673eaf4bab4fc5b598ad923afb46a77de

As can be seen, the ASN.1/DER encoded X.509/SPKI key contains the uncompressed public key at the end (the last 65 bytes).


Background:
Keep in mind that a public EC key is a point (x, y) on an EC curve (obtained by multiplying the private key by the generator point) and that there are different formats for its representation, e.g. the following two:

  • The uncompressed format, which corresponds to the concatenation of a 0x04 byte, and the x and y coordinates of the point: 04|x|y.
    For the secp256r1 curve (aka prime256v1 aka NIST P-256), x and y are both 32 bytes, so the uncompressed key is 65 bytes.
  • The X.509/SPKI format as defined in RFC 5280. This format is described with ASN.1 and serialized/encoded with DER (s. here).
    PublicKey#getEncoded() returns the ASN.1/DER encoded X.509/SPKI key. With an ASN.1/DER parser the ASN.1/DER can be decoded, e.g. https://lapo.it/asn1js/.

答案2

得分: 0

根据Topaco答案,您可以通过向SecKeyCopyExternalRepresentation的结果添加ASN.1/DER头来从iOS端解决此问题。

func pubKeyDataForAndroid(keyData: CFData) -> CFData {
    guard let mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFIndex(0)) else {
        fatalError("无法创建密钥数据")
    }
    var headerBytes = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00] as [UInt8]
    let headerSize = 26
    CFDataAppendBytes(mutableData, &headerBytes, headerSize)
    CFDataAppendBytes(mutableData, CFDataGetBytePtr(keyData), CFDataGetLength(keyData))
    return mutableData as CFData
}

调用此方法后,您可以将结果转换为base64并将其发送到Android。最后,将base64字符串转换为Java PubKey,这应该生成相同的密钥。

英文:

Based on Topaco and this answer, you can fix the issue from the iOS side by adding the ASN.1/DER header to the SecKeyCopyExternalRepresentation result.

func pubKeyDataForAndroid(keyData: CFData) -> CFData {
    guard let mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFIndex(0)) else {
        fatalError("Can not create key data")
    }
    var headerBytes = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00] as [UInt8]
    let headerSize = 26
    CFDataAppendBytes(mutableData, &headerBytes, headerSize)
    CFDataAppendBytes(mutableData, CFDataGetBytePtr(keyData), CFDataGetLength(keyData))
    return mutableData as CFData
}

After calling this method, you can convert the result to base64 and send it to Android. Finally, convert the base64 string to a Java PubKey, which should produce identical keys.

huangapple
  • 本文由 发表于 2023年3月4日 03:21:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/75631098.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定