Some libraries require that a public key is represented by an integer modulus and exponent. It's surprisingly hard to quickly convert a certificate or public key into that format. How can I convert that using bash?
Convert Certificate to Modulus, Exponent
The process for taking a certificate or a public key and turning it into integers is a surprisingly tough task as first glance. There's a long chain of decoding and encoding to various format that this page will describe.
1. Certificate to Public Key
$ CERTIFICATE_PATH=/path/to/file.cer
$ OUTPUT_PUBLIC_KEY_PATH=public_key.pem
$ openssl x509 -pubkey -noout -in $CERTIFICATE_PATH > $OUTPUT_PUBLIC_KEY_PATH
Creates the a public key (example output below, don't worry this is a sample cert, not real).
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArDMy/rsEEUfMQrrCSQmc
Lo9CSTvoL2jijXN1yi8jlx86yorxxm8Njdo0R2N6U0QRBLjglabeJJJZLJxQqPpU
RRFB7euTmmdLsctHxYVn2UDglIXV1Q5CGkSi9nFRupkL7ZMUflP0hqvPFRagUHaG
exJSlsO+32Gy6TxVIQKxAacjoYFJcMDGlDV4oTPelgMJ7enA7s4Bnz2tr6KJyoD3
N9fhcmFR0lGR7W7wqR79ePaxmPcCEl6UGAIbPny8WSQluRGzjsdB56dwsvdIZtlS
ChYr4tzoQYOB7cWc9gnJlQFOhocc/7tFPVXelT2Ff95Kp4eWGUK5JmzoU720B8Yk
EQIDAQAB
-----END PUBLIC KEY-----
2. Get Hexidecimal Modulus from Public Ke
$ HEX_MODULUS=$(openssl rsa -pubin -in $OUTPUT_PUBLIC_KEY_PATH -modulus | grep 'Modulus=' | cut -d'=' -f 2)
Creates the a hexidecimal representation of the modulus (example output below).
DD6DBDF880FAD7DE1B1FA7A32EB202E216F6520A3CBFA642F8CADC93674D60C34F8DC38A001BF1C44B416A69D269E53F218EC50BF82237ADB62C4B55FF7A0372BB9AD3EC96B9569FCB1999C932946F8FC652069F4503DFFDE897F6EAD6BABB482BB5E034614D52360FAB87522503CF87008713F2CA0329169D905746B5F40EAE17C80A4D92ED08A632231171FEF22C44D76C07F30B7B0C4BDD3BB4F737709F51B6884E5D6A057F8D9B667AAB8020FEEE6B97C3497D783BD599970375CE8FBCC5BE9C9AA5121970F9A4BD9627ED2302A7C757C971CF7694A22162F6B81DCA88EE09AD462FB761B32C1513869FA535265A67F437C8E68001490EC7ED61D3CDBCE4F8BE3FC94EF87D9789CE12BCCAB5C6D2E0D9B3683C2E4A9DB45FB853EE503DBFDDD4A28AB6A027AB980CB3B25890E2BCA1ADFFBD8E55310F00BF68E93DA9199AF06D0BA2146AC64CC64EBD6312A50B4D97EB42097953E265AA243470B8C1AB2380E79C6CEDDC82AA3704B8432A3D2AA8CC20FC275D902658F9B714E29EE2C1707397E96B028ED352597B00EC6130F1563F9CC17C05C5B136C81885CF61401F07E8A70687DF9A770BA9647203F693FCE00259C196ECC009423E30A27F1B482FFEE0218F5387250DCBEA49F54A9BD0E35FEE7818E5BA7131A904980FB1AD6752A0F2E39CAB6AFE588484DD073D3294051645159659A0586C180EE37766C7B3F799
3. Convert Hexadecimal Modulus to Decimal
$ ruby<<EOF
p "$HEX_MODULUS".to_i(16).to_s(10)
EOF
Converts the hexadecimal representation of the modulus to decimal (example output below).
"903350200741846433103490275702318092987711221795080780487705515793447642462352293930005029082816456324966429635984183642976753224939100182934586764830322547353276613511778555156314685902120045785748740652102149499802458636594115145098376676532039811405280029484180531080897744797671946602535748106482573745640230125725071851411418858523441305542960197934970282201978169899128421358221136498561735378441078904842496000031288381427214662674876164488503477712353985285783038861704346173846417987800700372136855065513839371230505066979655945991689080601315943347208545322825551901523372801711788730312786616209024413727099938085068143933277702040068400770002392724273077692527618673710507253235031640666038293670466879460643091688898398212470614257430485567355559709027230476557173161015264993692101931219217542706455704780898322106052146131050344223354172417918892290877128904693397579183166821072452886577300287605913021934410696419333315789150404571415571510150759313347067018155614134697544366299631877615175466702747992499691378170723240805893694504266094416954653431726471214487875228995333970243486471724362065194634035562833627383830413785525590101462126279287790122982746853775543354375554949279704170225740172092202881398339481"
4. Get Exponent from Public Key
$ openssl rsa -pubin -in $FILE -text | grep 'Exponent'
Gets the decimal exponent from the public key (example output below). Most people use 65537 as the exponent.
Exponent: 65537 (0x10001)
Related
I need to generate weak certificate for CTF challenge using RSA and small modulus so it's factorable. It should be about 64 bits.
I've tried to generate that using OpenSSL as I would the normal one, but it forbids creating certificate with modulus lower than 512 bits for security reasons. So I've changed the source files of OpenSSL so it doesn't checks the bit length and recompiled that again. I was then able to create private key using smaller modulus, but trying to create certificate using that key (or new small one) evoked new error which I don't fully understand. I even wanted to evade the OpenSSL problem at all using Python, but that just showed that it uses OpenSSL too and had exactly same problems.
Generating small private key:
$ openssl genrsa -out acn.pem 64
-----BEGIN PRIVATE KEY-----
MFQCAQAwDQYJKoZIhvcNAQEBBQAEQDA+AgEAAgkAlz0xJ3uUx5UCAwEAAQIIR1Zs
1Wo4EQECBQDHPJNVAgUAwlPjQQIFAKqNunkCBClt4QECBHlHx1Q=
-----END PRIVATE KEY-----
Generating certificate:
$ openssl req -key acn.pem -new -out domain.csr
...
140561480598848:error:04075070:rsa routines:RSA_sign:digest too big for rsa key:../crypto/rsa/rsa_sign.c:100:
140561480598848:error:0D0DC006:asn1 encoding routines:ASN1_item_sign_ctx:EVP lib:../crypto/asn1/a_sign.c:224:
I found that this thread could be helpful as I could even choose my own numbers, but the method didn't worked for me:
How to Generate rsa keys using specific input numbers in openssl?
Here's sample certificate from PicoCTF. I would like to create similar one.
-----BEGIN CERTIFICATE-----
MIIB6zCB1AICMDkwDQYJKoZIhvcNAQECBQAwEjEQMA4GA1UEAxMHUGljb0NURjAe
Fw0xOTA3MDgwNzIxMThaFw0xOTA2MjYxNzM0MzhaMGcxEDAOBgNVBAsTB1BpY29D
VEYxEDAOBgNVBAoTB1BpY29DVEYxEDAOBgNVBAcTB1BpY29DVEYxEDAOBgNVBAgT
B1BpY29DVEYxCzAJBgNVBAYTAlVTMRAwDgYDVQQDEwdQaWNvQ1RGMCIwDQYJKoZI
hvcNAQEBBQADEQAwDgIHEaTUUhKxfwIDAQABMA0GCSqGSIb3DQEBAgUAA4IBAQAH
al1hMsGeBb3rd/Oq+7uDguueopOvDC864hrpdGubgtjv/hrIsph7FtxM2B4rkkyA
eIV708y31HIplCLruxFdspqvfGvLsCynkYfsY70i6I/dOA6l4Qq/NdmkPDx7edqO
T/zK4jhnRafebqJucXFH8Ak+G6ASNRWhKfFZJTWj5CoyTMIutLU9lDiTXng3rDU1
BhXg04ei1jvAf0UrtpeOA6jUyeCLaKDFRbrOm35xI79r28yO8ng1UAzTRclvkORt
b8LMxw7e+vdIntBGqf7T25PLn/MycGPPvNXyIsTzvvY/MXXJHnAqpI5DlqwzbRHz
q16/S1WLvzg4PsElmv1f
-----END CERTIFICATE-----
I need to generate weak certificate for CTF challenge using RSA and small modulus so it's factorable. It should be about 64 bits.
It's impossible to do that as a self-signed certificate, because proper RSA signing can't work with keys that small.
RSA-SSA-PKCS1_v1.5 is the shortest structured RSA signature padding, and it's structured as (per https://datatracker.ietf.org/doc/html/rfc8017#section-9.2):
DigestInfo ::= SEQUENCE {
digestAlgorithm AlgorithmIdentifier,
digest OCTET STRING
}
The shortest possible encoding for that structure is 9 bytes... and that's with a 0-byte hash algorithm:
30 07 // SEQUENCE (7 content bytes)
30 03 // digestAlgorithm: SEQUENCE (3 bytes)
06 01 00 // OBJECT IDENTIFIER 0.0 ({itu-t(0) recommendation(0)})
// omit implicit NULL
04 00 // digest (empty)
So, with our empty hash we need to keep going:
If emLen < tLen + 11, output "intended encoded message length too short" and stop.
emLen is the length of the modulus (in bytes) and tLen is the length of our encoded structure (9 bytes+).
That makes 20 bytes (160 bits) the shortest possible RSA key to do anything that might stand a chance of being regarded as an RSA signature... which produced a pointless signature (since everything collides under a 0-bit hash).
If you are comfortable stomping on a 1-byte OID for your CTF, your RSA key modulus would need to be 20 bytes + the length of the intended hash (in bytes). Since there's no 1-byte OID that identifies an existing hash algorithm (and no defined hash algorithm for use with certificates that's that small), no existing easy-to-use tool can do this for you.
You could invent a new form of RSA signature padding, of course, using something like a 60-bit hash processed directly with your 64-bit key. That'll require even further work on your part.
You're basically reduced to using a DER writer (or writing your own) to hand-craft a certificate.
Here's sample certificate from PicoCTF. I would like to create similar one.
That certificate is not self-signed. It has a 2048-bit certificate signature, from RSA-SSA-PKCS1_v1.5 with MD2. So while it describes a short RSA key (53 bit modulus) it was signed with something "proper". If that's what you're after, the general flow would be something like
Create a normal self-signed cert
Create your small RSA key
Build a new cert, signed by the first cert, containing the small RSA key.
It's hard to encode a CSR for the small key (because it can't self-sign the request), but maybe there's a way to get openssl req -new to do something other than self-sign, which would allow skipping the intermediate CSR. (Or use library tools like .NET's CertificateRequest, or OpenSSL's APIs instead of their CLI tool, or whatever.)
Given an arbitrary (valid!) private or public key encoded inside of a PEM, with the pre-encapsulation boundary and post-encapsulation boundaries intact, is it possible to know exactly what format the bytes take (i.e. are they OpenSSL traditional, PKCS8, X.509 SubjectPublicKeyInfo, etc.), or does one need some a priori information to properly decode them?
With certificates the situation is almost straightforward - there the boundary line specifies what is expected (a certificate or a private key).
In OpenPGP armored data the boundary line also tells you what is expected - the key(s) or the data.
SSH keys created by several SSH applications have the same boundary lines but different format of the key itself. So you need to try reading the data in all expected formats.
RSA public keys usually have RSA 1.5 format so you can assume that you have an RSA key.
PKCS#12 is not usually wrapped to PEM (I never saw such files). The same goes for PKCS8.
PKCS#7 certificate storages are sometimes PEM-encoded and they have something like BEGIN CERTIFICATE STORAGE in their boundary line.
To sum it up - to some extent you can rely on the boundary line text, but this doens't give you a 100% guarantee.
You should have a look at the specification, PKCS#8 is specified in RFC5958 in Section 5
When .p8 files are PEM encoded they use the .pem file extension.
PEM encoding is either the Base64 encoding of the DER-encoded
EncryptedPrivateKeyInfo sandwiched between:
-----BEGIN ENCRYPTED PRIVATE KEY-----
-----END ENCRYPTED PRIVATE KEY-----
or the Base64 encoding, see Section 4 of [RFC4648], of the DER-encoded PrivateKeyInfo sandwiched between:
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY----
Slightly longer answer: "Between the tags is in any case valid Base64 Encoded DER encoded ASN.1"
I went through the math in the "worked example" in the RSA wiki page: https://en.wikipedia.org/wiki/RSA_(algorithm) and understood it entirely. For the remainder of this question, I will use math variables consistent with the wiki page.
I'm on a Unix machine and I'm looking in the ~/.ssh directory and I see all these files
id_rsa
id_rsa.pub
and I want to connect the theory with the practice.
What exactly is in id_rsa? If I cat it
cat id_rsa
I get a big jumble of characters. Is this some representation the number n = pq? What representation is it exactly? base 64? If so, then is id_rsa.pub suppose to be some representation of the numbers e and n?
In general, I'm trying to connect the theory of RSA with the actual practice as implemented through the ssh program on Unix machines. Any answers or pointers to the right direction would be greatly appreciated.
id_rsa is a base64-encoded DER-encoded string. The ASN.1 syntax for that DER-encoded string is described in RFC3447 (aka PKCS1):
Version ::= INTEGER { two-prime(0), multi(1) }
(CONSTRAINED BY
{-- version must be multi if otherPrimeInfos present --})
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
DER encoding uses a tag-length-value notation. So here's a sample private key:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp
wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5
1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh
3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2
pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX
GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il
AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF
L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k
X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl
U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=
-----END RSA PRIVATE KEY-----
Here's the hex encoding:
3082025c02010002818100aa18aba43b50deef38598faf87d2ab634e4571c130a9bca7b878267414
faab8b471bd8965f5c9fc3818485eaf529c26246f3055064a8de19c8c338be5496cbaeb059dc0b35
8143b44a35449eb264113121a455bd7fde3fac919e94b56fb9bb4f651cdb23ead439d6cd523eb081
91e75b35fd13a7419b3090f24787bd4f4e196702030100010281801628e4a39ebea86c8df0cd1157
2691017cfefb14ea1c12e1dedc7856032dad0f961200a38684f0a36dca30102e2464989d19a80593
3794c7d329ebc890089d3c4c6f602766e5d62add74e82e490bbf92f6a482153853031be2844a7005
57b97673e727cd1316d3e6fa7fc991d4227366ec552cbe90d367ef2e2e79fe66d26311024100de03
0e9f8884171ae90123878c659b789ec732da8d762b26277abdd5a68784f8da76abe677a6f00c77f6
8dcd0fd6f56688f8d45f731509ae67cfc081a6eb78a5024100c422f91d06f66d0af8072a2b70c5a6
fe110fd8c67344e57bdf2178d613ec442f66eba2ab85e3bd1cf4c9ba8dfff6ce69faca86c4e9452f
4343b784a4a2c8e01b0240164972475b99ff03c98e3eb5d5c741733b653ddaa8c6cb101a787ce41c
c28ffbb75aa069136be3bf2cafc88e645face4ed2d258cab6dda39f2dbed3456c05ead0241009182
d4c8393b2768e4dc03e818913ab3f11a8d9ba536eefdf86b4fc79b1e44f3d9ea6553d55041243363
5a193155fc8b59b95944cb3f3db22c9201415757aa13024011a88ae4a84a369f52157b8b57041a96
fcf21e4d058673597199dfbb09e50b16fac272a0d75edf11fcbdd5e1cd4ede4fcd83e97fec730f51
673fbfeab089e29d
The 30 is because it's a SEQUENCE tag. The 82025c represents the length. The first byte means the length is of the "long form" (82 & 80) and that the next two bytes represent the length (82 & 7F). So the actual length of the SEQUENCE is 025c. So after that is the value.
Then you get to the version. 02 is of type int, 01 is the tag length and 00 is the value. ie. it's a two-prime key as opposed to a multi-prime key.
More info on the Distinguished Encoding Rules.
Trying to understand ASN.1 is a lot more complicated and a lot of it, for the purpose of understanding the formatting of RSA private keys, is unnecessary. For X.509 it becomes more necessary but RSA keys aren't nearly as complicated, formatting-wise, as X.509 certs.
Hope that helps!
I have a 256-bit private key that I want to use to sign a SHA-1 digest (20 bytes). Using openssl directly it seems to work
echo doesntmatter | openssl dgst -sha1 -binary | openssl rsautl -sign -inkey 256bit_private_key.pem | openssl enc -base64
gives me a Base64 output as expected.
But doing it with the OpenSSL fails with "error:04075070:rsa routines:RSA_sign:digest too big for rsa key". As you can see below, I'm passing the 20-byte (SHA_DIGEST_LENGTH=20) SHA-1 digest as input to RSA_sign. Even with padding it shouldn't be more than the maximum of 32 bytes that I can encrypt with a 256 bit modulus key?!
unsigned char digest[SHA_DIGEST_LENGTH];
SHA1(message, messageSize, digest);
unsigned int privateKeySize = RSA_size(privateKey); // 256 bits = 32 bytes
unsigned char* signature = new unsigned char[privateKeySize];
unsigned int signatureSize;
int res = RSA_sign(NID_sha1, digest, SHA_DIGEST_LENGTH, signature, &signatureSize, privateKey);
if(res == 0)
{
int err = ERR_get_error(); // 67588208
char *s = ERR_error_string(err, 0); // error:04075070:lib(4):func(117):reason(112)
delete [] signature;
[...]
}
What am I doing wrong in the code?
check out this SO answer. rsautl is depreciated in favor of pkeyutl
Essentially, RSA signing should use RSA-PSS signature padding scheme to be secure, and the RSA-PSS will be strictly larger than the digest, because of the necessity to communicate a salt. additionally, RSA_sign does a digest as a part of the signing scheme, so your code is going to do a digest of a digest. you want to instead either pass the message in directly, or use RSA_private_encrypt combined with a suitable RSA_padding_add_x call.
and as I said in my comment, a 256-bit key is sufficiently secure for a symmetric algorithm, but it will be trivial to crack for an RSA private key (on the order of 4 minutes on a couple of year old machine). Using a bare minimum of a 1024 bit key (and probably a 2048 or 4096 bit key) will give you a security level roughly equivalent to a 128 bit symmetric algorithm.
Re Peter Elliott: not quite.
The PKCS1v1_5 standard sequence in RFC8017 section 8.2.1 (and 9.2, similar in rfc4347 and rfc3447) is hash, encode in ASN.1, pad ('type 1') and modexp d. (PSS omits the ASN.1 step and uses different padding.)
OpenSSL (low-level) RSA_sign does the last three (with v1.5 padding) but not the first -- i.e. it encodes the hash but it doesn't do the hash; even-lower RSA_private_encrypt does the last two.
The higher-level envelope module (original EVP_Sign{Init,Update,Final}*, enhanced EVP_DigestSign*) does all four.
It's the ASN.1 encode that made the value too large for a 256-bit key -- which as stated is breakable anyway.
rsautl does only the last two steps not the encode, so it runs but gives a nonstandard result.
pkeyutl for RSA does the same by default but can be told to do the encode.
Also, PSS has a better security proof, but as far as I've heard there's no actual attack on v1.5 and it's still widely used.
If you have the choice of PSS (both/all parties support it) choose it, but don't feel worried about using v1.5.
Guess I found the solution. openssl rsautl -sign uses RSA_private_encrypt instead of RSA_sign (what I would have expected). RSA_sign creates a longer structure than the 20-bytes message I provided, so it fails with the given error.
I have my RSA public and private keys (all of p,q,e,n,d) in PEM format. I am curious to know:
How PK11_Sign(), PK11_Verify() and PK11_VerifyRecover() (from OpenSSL/Mozilla NSS library) work with RSA?
How the padding is applied to the input message to be signed?
The context of my question is: I have seen PK11_Sign() adds some padding to my input data during signing. For example (given the key size is 162 bits):
my input = 31323334353036373839
padded input = 1FFFFFFFFFFFFFFFF0031323334353036373839
I would like to know:
What is the name of this padding scheme and pointers on how it works?
What is the default padding scheme for the above mentioned OpenSSL functions? For example, if I perform " openssl rsautl -in input.txt -inkey mykey.pem -out signed.txt ", which padding scheme will be used?
PK11_Sign etc. uses PKCS#1 v.1.5 signatures, which includes the padding you mention.
The padding scheme is part of the algorithm called EMSA-PKCS1-V1_5-ENCODE. I do not believe it has a name, although it might be informally called "PKCS#1 v.1.5 signature padding". It is defined in the PKCS#1 standard.
According to the documentation the default for openssl rsautl is to use PKCS#1 v.1.5 signature, which implies this padding.