英文:
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字符串不匹配,这表明在编码和解码公钥时可能存在问题。你可以尝试以下几种方法来解决这个问题:
-
确保输入的Base64字符串没有额外的空格或换行符。 在你的示例中,
pkIOS
的值被包围在"
字符中,这可能是不必要的。确保pkIOS
中只包含Base64编码的公钥。 -
检查编码和解码的方式。 你可以使用不同的方式来编码和解码Base64,例如
Base64.encodeToString()
和Base64.decode()
。确保在编码和解码时使用相同的方式和参数,以免出现不匹配的情况。 -
验证BouncyCastle库版本。 你已经提供了你使用的BouncyCastle版本,但要确保它与你的代码兼容。有时,不同版本的库可能会导致不一致的结果。如果可能的话,尝试更新到最新版本并查看是否有改进。
-
检查iOS设备上的公钥生成代码。 确保iOS设备上生成的Base64编码的公钥与你的预期相匹配。如果iOS端有问题,那么无论你如何处理,结果都会不一致。
-
调试输出。 你可以使用日志输出来调试代码,以查看生成的公钥是否与输入相匹配。这有助于确定问题出现在哪里。
最后,确保你理解公钥的格式和编码,以便更好地调试和解决问题。希望这些建议有助于解决你遇到的问题。如果问题仍然存在,请提供更多细节,以便获得更具体的帮助。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论