I'm trying to manually create an ES256 JWT token. I've a small script written in python which signs a sha256 hash which uses ecdsa-python. But the signature is invalid on jwt.io.
Steps to reproduce:
Create base64 header + payload:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
Create SHA256 hash from the base64 header + payload:
FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F
Generate ec_private key:
openssl ec -in ec_private.pem -noout -text
Use the small python program to ecdsa sign the SHA256 hash
from json import dumps
from ellipticcurve.ecdsa import Ecdsa
from ellipticcurve.privateKey import PrivateKey
import base64
def toBase64Url(input):
return input.replace("+", "-").replace("/", "_").rstrip("=")
# Generate privateKey from PEM string
privateKey = PrivateKey.fromPem("""
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49
AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa
Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END EC PRIVATE KEY-----
""")
# Create message from json
message = "FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F"
signature = Ecdsa.sign(message, privateKey)
# Generate Signature in base64. This result can be sent to Stark Bank in the request header as the Digital-Signature parameter.
print("Base64: " + signature.toBase64())
print("Base64Url: " +toBase64Url(signature.toBase64()))
# To double check if the message matches the signature, do this:
publicKey = privateKey.publicKey()
print("Hash verification succesfull: " + str(Ecdsa.verify(message, signature, publicKey)))
The output:
Base64: MEQCIFyP4IoZGhzGfDCPX6fVxjtB+nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF+IHzxk8em+OMMHrJkPOhA==
Base64Url: MEQCIFyP4IoZGhzGfDCPX6fVxjtB-nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF-IHzxk8em-OMMHrJkPOhA
Hash verification succesfull: True
Check the signature in jwt.io gives Invalid signature
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.MEQCIFyP4IoZGhzGfDCPX6fVxjtB-nrXDVhOTQwdc5vu8z4eAiBNalfGHqdaO3nCmTqimpAHF-IHzxk8em-OMMHrJkPOhA
Keys:
Public:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0
uoYcvX4Z7ROUIgYRvgfpsjBaIv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END PUBLIC KEY-----
Private:
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49
AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa
Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END EC PRIVATE KEY-----
I know that there are many jwt signing python libraries but the use of this is to understand how a jwt token is created.
EDIT:
As #Topaco pointed out this library uses curve secp256k1 instead of secp256r1. secp256r1 | prime256v1 | NIST P-256 are all different names chosen by different standards organizations for the same curve (Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)). I changed the library to python-ecdsa and the code to:
from ecdsa import SigningKey, NIST256p
import base64
def toBase64Url(input):
return input.replace("+", "-").replace("/", "_").rstrip("=")
sk = SigningKey.from_pem("""
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49
AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa
Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END EC PRIVATE KEY-----
""")
vk = VerifyingKey.from_pem("""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0
uoYcvX4Z7ROUIgYRvgfpsjBaIv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END PUBLIC KEY-----
""")
signature = sk.sign(b"FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F")
print(base64.b64encode(signature))
print("Base64: " + base64.b64encode(signature).decode("utf-8"))
print("Base64Url: " + toBase64Url(base64.b64encode(signature).decode("utf-8")))
assert vk.verify(signature, b"FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F")
print("Hash verification succesfull: " + str(vk.verify(signature, b"FFC89E33091FFDD3C61798A0A74BF7C2D1A6FD231A6CB519F33952F7696BBE9F")))
The output:
Base64: rMBgC0ismGdd5rd7n1L+LDsQ2UO5+cjBwPNYh+xBZvO6fKoJIfmfyNpxw+kxmyKWlK+55dF5eMH1u327DMJvvA==
Base64Url: rMBgC0ismGdd5rd7n1L-LDsQ2UO5-cjBwPNYh-xBZvO6fKoJIfmfyNpxw-kxmyKWlK-55dF5eMH1u327DMJvvA
Hash verification succesfull: True
But the signature is still invalid.
The library you are using hashes implicitly, applying SHA1 by default. I.e. for compatibility with ES256 SHA256 must be explicitly specified and the unhashed JWT must be used, e.g.:
from ecdsa import SigningKey, VerifyingKey
import base64
from hashlib import sha256
def toBase64Url(input):
return input.replace("+", "-").replace("/", "_").rstrip("=")
jwt = b"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0"
sk = SigningKey.from_pem("""
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJfChy9fKFItzqcb8DKBm+2oH0YTZ7N61SQpyABgVZANoAoGCCqGSM49
AwEHoUQDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0uoYcvX4Z7ROUIgYRvgfpsjBa
Iv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END EC PRIVATE KEY-----
""")
vk = VerifyingKey.from_pem("""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1TG2uvIMdfWkteiDWeHNYbQNSW/0
uoYcvX4Z7ROUIgYRvgfpsjBaIv70SuYpmBLwl0AuEBoXIVTCclCme6SdEQ==
-----END PUBLIC KEY-----
""")
signature = sk.sign(jwt, hashfunc=sha256)
print("Base64: " + base64.b64encode(signature).decode("utf-8"))
print("Base64Url: " + toBase64Url(base64.b64encode(signature).decode("utf-8")))
assert vk.verify(signature, jwt, hashfunc=sha256)
print("Hash verification succesfull: " + str(vk.verify(signature, jwt, hashfunc=sha256)))
A possible output is:
Base64: Mr4/DF87ek66E2GcAc+2H3ulHplCnxygz65h9dkdvm8QsZBbm2N5EjIgyiWsynza9zCGjjnzBUiXYvij9LLikA==
Base64Url: Mr4_DF87ek66E2GcAc-2H3ulHplCnxygz65h9dkdvm8QsZBbm2N5EjIgyiWsynza9zCGjjnzBUiXYvij9LLikA
Hash verification succesfull: True
The resulting signed token
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.Mr4_DF87ek66E2GcAc-2H3ulHplCnxygz65h9dkdvm8QsZBbm2N5EjIgyiWsynza9zCGjjnzBUiXYvij9LLikA
can then be successfully verified on https://jwt.io/ with the public key used here.
Related
I'm generating 2048-bit RSA public and private keys, encoding them in X509 format, converting the result to PKCS#1 format, encoding that as a Base64 string and saving it in a database. Then I retrieve the string from the database, restore it to PKCS#1 format, convert it to X509 format, and then restore the public key object.
When I call println() on the original and the restored public key objects, I confirm that they have the same modulus and public exponent. When I check their equality with .equals(), the assertion returns true. However, when I compare the X509 encoded representation of the original public key object and that of the restored public key object, they are different. This is problematic for me because I use a hash derived from this value in order to identify the keys.
Why is this happening, and how can I fix it?
I must store keys in the database in PKCS#1 format because the database file will be moved to an iOS device the keys will be restored there, and Apple's Security framework only allows the export or import of RSA keys in PKCS#1 format.
I'm using Kotlin on JVM; the code below reproduces the issue:
import org.bouncycastle.asn1.ASN1Primitive
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.*
import kotlin.test.Test
class IssueExample {
#Test
fun demonstrateProblem() {
//Generate RSA key pair
val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
//Convert public key to X509, then to PKCS1 format
val pubKeyX509Bytes = keyPair.public.encoded
val sPubKeyInfo: SubjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pubKeyX509Bytes)
val pubKeyPrimitive: ASN1Primitive = sPubKeyInfo.parsePublicKey()
val pubKeyPkcs1Bytes: ByteArray = pubKeyPrimitive.encoded
//Convert PKCS1 to Base64 string
val encoder = Base64.getEncoder()
val base64PubKey = encoder.encodeToString(pubKeyX509Bytes)
//Restore Base64 string to PKCS#1
val decoder = Base64.getDecoder()
val restoredPubKeyPKCS1Bytes = decoder.decode(base64PubKey)
//Restore PKCS#1 to X509
val algorithmIdentifier: AlgorithmIdentifier = AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
val restoredPubKeyX509Bytes: ByteArray = SubjectPublicKeyInfo(algorithmIdentifier, pubKeyPkcs1Bytes).encoded
//Restore X509 tp public key
val restoredPubKey: PublicKey = KeyFactory.getInstance("RSA").generatePublic(X509EncodedKeySpec(pubKeyX509Bytes))
//Comparing the two public key objects returns true
println(restoredPubKey.equals(keyPair.public))
//And calling println() yields identical results for both
println(restoredPubKey)
println(keyPair.public)
//Yet their X509 representations return false!!
println(restoredPubKey.encoded.equals(keyPair.public.encoded))
println(restoredPubKey.encoded)
println(keyPair.public.encoded)
}
}
I'm attempting to import an RSA Public key into dotnet with the following:
var rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(PublicKey), out _);
key was was generated with:
openssl genrsa -out name_of_private_key.pem 2048
openssl rsa -in name_of_private_key.pem -pubout > name_of_public_key.pem
output:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtZL7iKRPSxrCflER6j/I
wB9fODXJgfxR4UBSU3oUJ8tIaBBnDrcutfXDfc7lZ9HcCZccvUsMzFKGJuvHthCE
/LNJmZtRRd02aLynoZSWqDBerCdRqXHbecMfK8KPxQSsWfinNiyFG76vTX2+V8P6
t4Cu8bM8j7foSBgOmECCSOjTuCG4bvKVS3bnu2lSBNgCjEMltk9W/3oSzKbN/mwn
GfViaXU5a1Zps3jLbx/z58o3Sb25QfQKU4xeohcx+Wj6d14lI80RErS1QTqSQ1rz
10Cs/Q1MudWstckqyE/u048GtXzQCzQOe4hWlyrcFqfiEAbV2jPLU61oer4/wT+0
7QIDAQAB
-----END PUBLIC KEY-----
However this returns
System.Security.Cryptography.CryptographicException: ASN1 corrupted data.
To import the key I'm taking the text between the headers and removing newlines, nothing else. I have noticed that rsa.ImportSubjectPublicKeyInfo DOES appear to work, however I'm not attempting to generate an X.509 key, I would like a PKCS#1 key so I can use the code above.
Guessing I've messed up the openssl commands?
You can convert the posted X.509/SPKI key to a PKCS#1 public key using the following OpenSSL statement:
openssl rsa -pubin -RSAPublicKey_out -in name_of_public_key.pem > name_of_public_key_conv_pkcs1.pem
This returns the following key for name_of_public_key_conv_pkcs1.pem:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAtZL7iKRPSxrCflER6j/IwB9fODXJgfxR4UBSU3oUJ8tIaBBnDrcu
tfXDfc7lZ9HcCZccvUsMzFKGJuvHthCE/LNJmZtRRd02aLynoZSWqDBerCdRqXHb
ecMfK8KPxQSsWfinNiyFG76vTX2+V8P6t4Cu8bM8j7foSBgOmECCSOjTuCG4bvKV
S3bnu2lSBNgCjEMltk9W/3oSzKbN/mwnGfViaXU5a1Zps3jLbx/z58o3Sb25QfQK
U4xeohcx+Wj6d14lI80RErS1QTqSQ1rz10Cs/Q1MudWstckqyE/u048GtXzQCzQO
e4hWlyrcFqfiEAbV2jPLU61oer4/wT+07QIDAQAB
-----END RSA PUBLIC KEY-----
Alternatively, you can generate a PKCS#1 public key directly using the following OpenSSL statements:
openssl genrsa -out name_of_private_key.pem 2048
openssl rsa -in name_of_private_key.pem -RSAPublicKey_out > name_of_public_key_pkcs1.pem
Public keys in PKCS#1 format can be imported with the code you posted, e.g.
var PublicKey = #"-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAtZL7iKRPSxrCflER6j/IwB9fODXJgfxR4UBSU3oUJ8tIaBBnDrcu
tfXDfc7lZ9HcCZccvUsMzFKGJuvHthCE/LNJmZtRRd02aLynoZSWqDBerCdRqXHb
ecMfK8KPxQSsWfinNiyFG76vTX2+V8P6t4Cu8bM8j7foSBgOmECCSOjTuCG4bvKV
S3bnu2lSBNgCjEMltk9W/3oSzKbN/mwnGfViaXU5a1Zps3jLbx/z58o3Sb25QfQK
U4xeohcx+Wj6d14lI80RErS1QTqSQ1rz10Cs/Q1MudWstckqyE/u048GtXzQCzQO
e4hWlyrcFqfiEAbV2jPLU61oer4/wT+07QIDAQAB
-----END RSA PUBLIC KEY-----".
Replace("-----BEGIN RSA PUBLIC KEY-----", "").
Replace("-----END RSA PUBLIC KEY-----", "").
Replace("\r\n", "");
var rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(PublicKey), out _);
RSAParameters parameters = rsa.ExportParameters(false);
Console.WriteLine(new BigInteger(parameters.Exponent, true, true)); // 65537
Console.WriteLine(new BigInteger(parameters.Modulus, true, true)); // 22921612997464368147681940553984745387167552018036344531503795467063837226615581953768444015539628345845035732103113334279875993301411098168640007990192163617624452836576802897196284289413557038039593995983320236405640276117810563150914793233680115042600127677172037054986051882799772185194759951925398974095268701931531156047608941244890064857847352301510189736406400522269201574332107656671336685945934753045233371160604896169352804846566979618872110365310073347596127824815830796826711019699206801083371733500629381548849681219453339114997443300562712444634750316194264179142382642144192449752430619501209065600237
for the implementation of an API I use, I need to provide a certificate, which consists of 2 byte arrays one for the public key and the other one for private key.
My initial idea was to do this with X509Certificate object of .Net. But I am struggling to get the private key bytes.
var certificate = new X509Certificate2("testCert.pfx", password, X509KeyStorageFlags.Exportable);
byte[] myPublicKey = certificate.GetRawCertData();
byte[] privateKey = ???
I've tried to export the key, but I can't export the private key standalone.
And:
certificate.PrivateKey.ToXmlString(true);
is not available on a Ubuntu System :-(
Do you have any ideas, how to get the private bytes from certificates?
May be X509Certificate2 is not the best solution for this...
Use an approrpiate method of these X509Certificate2 extension methods:
GetRSAPrivateKey(X509Certificate2) -- for RSA keys
GetDSAPrivateKey(X509Certificate2) -- for DSA keys
GetECDsaPrivateKey(X509Certificate2) -- for EC keys
Extension method you need to use depends on asymmtric key algorithm.
I have X509 Signing Certificate inside of a string like:
var signingCertificate = -----BEGIN CERTIFICATE-----\r\nMIICTjCCAbegAw.........-----END CERTIFICATE-----
Now I want to read the content of this certificate. i know we can do it using X509Certificate2 object but that reads from file directly. Is there anyway to read the content from string?
You can convert your string to byte array, and create a X509Certificate2 object from it.
byte[] bytes = Encoding.ASCII.GetBytes(signingCertificate);
var x509Certificate2 = new X509Certificate2(bytes);
I am attempting to export the contents of a PrivateKey (assumed DER) format into a stream of bytes encoded as PEM format.
The link here discusses the use of BouncyCastle and PemWriter to dump a key to PEM format.
Is there a way to create a BouncyCastle object from a PrivateKey object, or some other way to export PrivateKey as PEM byte stream?
The code snippet seems to do the job:
KeyStore keystore = KeyStore.Instance("JKS");
InputStream stream = new FileInputStream("path-to-jks-file");
keystore.load(stream, null);
PrivateKey key = (PrivateKey) keystore.getKey("mykey", "password".toCharArray());
byte[] prvkey = key.getEncoded();
String encoded = Base64.getEncoder().encodeToString(prvkey);
String key_pem = "-----BEGIN PRIVATE KEY-----" + encoded + "-----END PRIVATE KEY-----";