What the F*@K is a Base64 DER ASN.1 (a.k.a. a PEM key)?

Joseph Hollingworth
6 min readJul 10, 2021

So like all too many of my stories this starts with me being a little over ambitious and more than a little naïve. Since I moved fully into the world of computer science and software development I have slowly become more and more fascinated by security. From writing secure code, creating and testing infrastructure, to malware analysis and attacking (with permission) digital targets; I have dived deeply into the world of Cyber & Information Security.

There is, however, one part of InfoSec that has enthralled me more than any-other, cryptology. Lacking the mathematical and experiential knowledge to be a cryptographer, for now, I have been diligently learning how to analyse and decrypt cipher-text (otherwise known as ‘cryptanalysis’, or sometimes ‘hacking’!). After making sure I remembered a few key algorithms (Euclidean, Extended Euclidean, Diffie-Hellman) I decided that I was ready to start cracking. Starting off simple I went through CryptoHack’s earliest challenges and progressed at a pace that made my ego feel good, but not too good.

Then I hit the Data Formats section… PEM keys, I’ve used this at work! This will be a doddle, downloaded the file and starting at me was an all too familiar format:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzvKDt+EO+A6oE1LItSunkWJ8vN6Tgcu8Ck077joGDfG2NtxD
4vyQxGTQngr6jEKJuVz2MIwDcdXtFLIF+ISX9HfALQ3yiedNS80n/TR1BNcJSlzI
uqLmFxddmjmfUvHFuFLvxgXRga3mg3r7olTW+1fxOS0ZVeDJqFCaORRvoAYOgLgu
d2/E0aaaJi9cN7CjmdJ7Q3m6ryGuCwqEvZ1KgVWWa7fKcFopnl/fcsSecwbDV5hW
fmvxiAUJy1mNSPwkf5YhGQ+83g9N588RpLLMXmgt6KimtiWnJsqtDPRlY4Bjxdpu
V3QyUdo2ymqnquZnE/vlU/hn6/s8+ctdTqfSCwIDAQABAoIBAHw7HVNPKZtDwSYI
djA8CpW+F7+Rpd8vHKzafHWgI25PgeEhDSfAEm+zTYDyekGk1+SMp8Ww54h4sZ/Q
1sC/aDD7ikQBsW2TitVMTQs1aGIFbLBVTrKrg5CtGCWzHa+/L8BdGU84wvIkINMh
CtoCMCQmQMrgBeuFy8jcyhgl6nSW2bFwxcv+NU/hmmMQK4LzjV18JRc1IIuDpUJA
kn+JmEjBal/nDOlQ2v97+fS3G1mBAaUgSM0wwWy5lDMLEFktLJXU0OV59Sh/90qI
Jo0DiWmMj3ua6BPzkkaJPQJmHPCNnLzsn3Is920OlvHhdzfins6GdnZ8tuHfDb0t
cx7YSLECgYEA7ftHFeupO8TCy+cSyAgQJ8yGqNKNLHjJcg5t5vaAMeDjT/pe7w/R
0IWuScCoADiL9+6YqUp34RgeYDkks7O7nc6XuABi8oMMjxGYPfrdVfH5zlNimS4U
wl93bvfazutxnhz58vYvS6bQA95NQn7rWk2YFWRPzhJVkxvfK6N/x6cCgYEA3p21
w10lYvHNNiI0KBjHvroDMyB+39vD8mSObRQQuJFJdKWuMq+o5OrkC0KtpYZ+Gw4z
L9DQosip3hrb7b2B+bq0yP7Izj5mAVXizQTGkluT/YivvgXcxVKoNuNTqTEgmyOh
Pn6w+PqRnESsSFzjfWrahTCrVomcZmnUTFh0rv0CgYBETN68+tKqNbFWhe4M/Mtu
MLPhFfSwc8YU9vEx3UMzjYCPvqKqZ9bmyscXobRVw+Tf9llYFOhM8Pge06el74qE
IvvGMk4zncrn8LvJ5grKFNWGEsZ0ghYxJucHMRlaU5ZbM6PEyEUQqEKBKbbww65W
T3i7gvuof/iRbOljA9yzdwKBgQDT9Pc+Fu7k4XNRCon8b3OnnjYztMn4XKeZn7KY
GtW81eBJpwJQEj5OD3OnYQoyovZozkFgUoKDq2lJJuul1ZzuaJ1/Dk+lR3YZ6Wtz
ZwumCHnEmSMzWyOT4Rp2gEWEv1jbPbZl6XyY4wJG9n/OulqDbHy4+dj5ITb/r93J
/yLCBQKBgHa8XYMLzH63Ieh69VZF/7jO3d3lZ4LlMEYT0BF7synfe9q6x7s0ia9b
f6/QCkmOxPC868qhOMgSS48L+TMKmQNQSm9b9oy2ILlLA0KDsX5O/Foyiz1scwr7
nh6tZ+tVQCRvFviIEGkaXdEiBN4eTbcjfc5md/u9eA5N21Pzgd/G
-----END RSA PRIVATE KEY-----

Well, crap.. What does this actually mean? In my head this had always been a singular key that I used in whatever NP network algorithm was being used to encrypt that communication. Guess what? I was very, very, wrong.

Before going any further this isn’t a step-by-step walk through on how to solve the challenge on CryptoHack, that wouldn’t be in the spirit of the game in my opinion. Yes the code is in my repo, but I would encourage anyone looking to learn cryptography to spend the time building it themselves. The reason why I’m posting this is while there is a *lot* of information on base64 DER ASN.1 most of it is very technical, to the point of obscurity, or not that useful. There are great tools for straight up decoding PEM keys (Holstrom has a great one that I used for cross validation). But most of my useful learnings came from reverse engineering the private key file. So here’s my breakdown, I hope it helps!

Starting with the header and footer, while these are important to more advanced programs (i.e. ones that actually need to use the keys) for our purposes we can just ignore them.

-----BEGIN RSA PRIVATE KEY-----
...........................
...........................
-----END RSA PRIVATE KEY-----

Now looking at the text, to anyone familiar with standard file encryption types you might recognise this as base64 and you’d be right, all we have here is some binary data encoded into human ‘readable’ text.

MIIEowIBAAKCAQEAzvKDt+EO+A6oE1LItSunkWJ8vN6Tgcu8Ck077joGDfG2NtxD
4vyQxGTQngr6jEKJuVz2MIwDcdXtFLIF+ISX9HfALQ3yiedNS80n/TR1BNcJSlzI
uqLmFxddmjmfUvHFuFLvxgXRga3mg3r7olTW+1f...

So what is this binary data (we’ll be treating it as hex for readability)? Specifically it is DER ASN.1; that being the ‘Distinguished Encoding Rules’ ASN.1. Ignoring the ASN.1 for now, DER is a highly specific agreement on how data can be represented. For our sake the most important things are:

  1. DER is a type-length-value encoding, so if I had the data 0x04 0x01 0xa4 my type would be 0x04, the length of my data is 0x01 hex (or a ‘nibble’) and my data is 0xa4.
  2. Length must always be the shortest value possible, i.e. how did I know the nibble 0x01 above meant 1 hex and not 1 octet (fancy way of saying byte). We will come back to this later

ASN.1 is simply the layout/format of the data:

An RSA private key shall have ASN.1 type RSAPrivateKey:

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 }

Version ::= INTEGER

The order and types of these will never change (you can use this to check your byte addition if you’re writing your own breakdown code) so it means we can easily loop through the file provided we understand how the length section of the encoding works.

Looking at any PEM private key, we can see the first three letters are `MII` which just so happens to be 0x30 0x82. Here 0x30 is our type (I’ll add the map of hex to type at end): “SEQUENCE”, as should come as no surprise from looking at the ASN.1 layout.

Now we might assume 0x82 is our length but in-fact it is letting us know we are looking at an octet length value given by the next byte. In simpler terms the next 2 hex values will tell us how long our data section is in bytes. Reading 0x04 0xa3 gives us 1187 bytes or 2374 nibbles. But, if we look at the length of the hex-data, however, we see it is 2382 nibbles long. Don’t worry, the length is always of the data to follow and we’re currently looking at nibbles 4–8 so this is telling us the length of hex-data[8:]. Thankfully 2374 + 8 is 2382 so we haven’t magically lost data, phew.

“SEQUENCE” is an important tag as after reading length we, go back to type parsing and move forward a nibble and read the value 0x02, and on our Type map this means we have an “INTEGER” and looking at the ASN.1 we can see that Version is indeed a masked INTEGER.

Moving forward one nibble again, we read 0x01. This tells us the following value data will be one nibble in length. So taking that next hex we can see out version is 0x00 so 0!

Now we have ave completed the type-length-value cycle, we jump back to type parsing for the next attribute, modulus. Moving forward a nibble and read 0x02 (“INTEGER” type as ASN.1 says we should for Modulus), move along a nibble, read 0x82 so we know the length will be the following two nibbles treated as an octet for 0x0101 (257 bytesor 564 nibbles). So our Modulus value is the following 564 nibbles!

And that’s it, we keep cycling through that process until we hit the end of our data and out pops all of the ASN.1 values!

For something that seemed quite complex, it ultimately becomes a loop with a few if statements. (Or you could do it as a recursive function, but my brain is thinking about more exciting things)

Now if you’re feeling a little lost that’s to be expected! I’m going to do a little walkthrough with just the first three parts of an imaginary key

The Public key works in the exact same way just with a few more nested sequences but as the interesting data there is all contained with the private key** I don’t feel the need to crack that as well.

To any budding cryptologists like me out there please feel free to ask any questions and to any experienced cryptologists if you think I’ve missed something or made a mistake please let me know!

*What is actually happening is every time we read the length value it is XOR’d with the values 128 and 127, if either returns 128 or 127 then we know to look for 2 or 1 nibbles, to be treated as an octet, specifically.

**The personal information contained in the pub key is normally on a web directory if the .pem file is being used as a cert, SSL or similar. Otherwise the information provided on your target is most likely known to you already.

Type Map

For DER these exact values are the only ones that can be matched. I.e. Booleans cannot be ‘truthy’ they must be 0x01

"BOOLEAN":          0x01,
"INTEGER": 0x02,
"BITSTRING": 0x03,
"OCTETSTRING": 0x04,
"NULL": 0x05,
"OBJECTIDENTIFIER": 0x06,
"ObjectDescriptor": 0x07,
"UTF8String": 0x0c,
"SEQUENCE": 0x10,
"SET": 0x11,
"NumericString": 0x12,
"PrintableString": 0x13,
"TeletexString": 0x14,
"IA5String": 0x16,
"UTCTime": 0x17,
"GeneralizedTime": 0x18

--

--

Joseph Hollingworth

MSc Computer Science Student and Philosophy Enthusiast