skip to content

Search

Case study: Regular Expressions

2 min read

Extracting Private and Public SSH Keys from a string.

Let’s say you have a string that contains both a private SSH key, and the public key, both in PEM format. How do you separate them from each other?

const content =
"Private Key:-----BEGIN OPENSSH PRIVATE KEY-----
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
YourPrivateSSHKeyYourPrivateSSHKeyYourPrivateSSHKey
-----END OPENSSH PRIVATE KEY-----
Public Key:-----BEGIN PUBLIC KEY-----
YourPublicSSHKeyYourPublicSSHKeyYourPublicSSHKey
YourPublicSSHKeyYourPublicSSHKeyYourPublicSSHKey
YourPublicSSHKeyYourPublicSSHKeyYourPublicSSHKey
YourPublicSSHKeyYourPublicSSHKeyYourPublicSSHKey
YourPublicSSHKeyYourPublicSSHKeyYourPublicSSHKey
-----END PUBLIC KEY-----"

One way is to use a regular expression which specifically targets their format. Private keys are almost always contained within a header and footer.

const privateSSHKey =
  /-----BEGIN(.+)PRIVATE KEY(.+)(\n.+)*END(.+)PRIVATE KEY-----/.exec(content);
const publicSSHKey =
  /-----BEGIN(.+)PUBLIC KEY(.+)(\n.+)*END(.+)PUBLIC KEY-----/.exec(content);

This can be cleaned up a bit:

const privateSSHKey =
  /-{5}BEGIN(.+)PRIVATE KEY(.+)(\n.+)*END(.+)PRIVATE KEY-{5}/.exec(content);
const publicSSHKey =
  /-{5}BEGIN(.+)PUBLIC KEY(.+)(\n.+)*END(.+)PUBLIC KEY-{5}/.exec(content);
  • -{5} exactly 5 instances of -
  • BEGIN exact match
  • (.+) - something could be here, if anything
    • . matches any character (except for line terminators)
    • + matches the previous token between one and unlimited times, as many times as possible, giving back as needed (greedy)
  • PRIVATE KEY exact match
  • (\n.+)*
    • * matches the previous token between zero and unlimited times, as many times as possible, giving back as needed (greedy)
    • \n matches a line-feed (newline) character (ASCII 10)
      • . matches any character (except for line terminators)
      • + matches the previous token between one and unlimited times, as many times as possible, giving back as needed (greedy)
  • END exact match
  • (.+) - something could be here, if anything
  • PRIVATE KEY exact match
  • -{5} exactly 5 instances of -
  • .exec() executes a search on a string using a regular expression pattern, and returns an array containing the results of that search.

Depending on the format the header and footer may have a varied string:

------BEGIN OPENSSH PRIVATE KEY------
 
------END OPENSSH PRIVATE KEY------
------BEGIN RSA PRIVATE KEY------
 
------END RSA PRIVATE KEY------
------BEGIN PRIVATE KEY------
 
------END PRIVATE KEY------
------BEGIN OPENSSH PUBLIC KEY------
 
------END OPENSSH PUBLIC KEY------
------BEGIN RSA PUBLIC KEY------
 
------END RSA PUBLIC KEY------
------BEGIN PUBLIC KEY------
 
------END PUBLIC KEY------

This is why we need to include (.+) between BEGIN and PRIVATE KEY .

What if the public key was in the ssh-rsa format?

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7jdirkf4o6 ...

You could do:

/ssh-rsa(.+)==/;

The values can then be assigned to a key/value pair within an object, for example:

{
	privateKey: privateSSHKey,
	publicKey: publicSSHKey
}

Resources:

https://regex101.com/

https://www.thedigitalcatonline.com/blog/2018/04/25/rsa-keys/