From ABBA to RSA and Ed25519 Key Wrapping

--

In 1977, the top of the charts in the UK was “Knowing Me, Knowing You”: But, ABBA wasn’t the only major thing happening. In April 1997, Rivest, Shamir and Adleman released the RSA method. And, so, 47 years later, RSA is still top of the charts, especially for public key encryption. But, there’s another contender for public key encryption, and that’s elliptic curve cryptography.

For many companies, the fundamental element of their cybersecurity is their SSH keys, and which are typically used to authenticate onto GitHub and to log into the Cloud. We can also use these keys to protect secret keys. This will allow us to extract a secret key from a secure enclave, which is encrypted with the public key. So, let’s wrap keys with RSA and Ed25519 key pairs.

Ed25519 key wrapping

Key wrapping allows us to protect a secret key. In this case, we will create an Ed25519 key pair and also a secret 128-bit symmetric key. We then wrap the symmetric key with the public key, and then unwrap with the private key. We can create the key pair with:

public, private, _ := ed25519.GenerateKey(nil)

This created two 32 byte arrays for the public key and the private key. The public key and the private key are both 256 bits long. The Golang code for this is [here]:

package main
import (
"fmt"
"crypto/rand"
"crypto/ed25519"
"encoding/base64"
"filippo.io/age/agessh"
"golang.org/x/crypto/ssh"
)
func main() {
public, private, _ := ed25519.GenerateKey(nil)
pub, _ := ssh.NewPublicKey(public)

r, _ := agessh.NewEd25519Recipient(pub)
i, _ := agessh.NewEd25519Identity(private)

fileKey := make([]byte, 16)
_, _ = rand.Read(fileKey)
stanzas, _ := r.Wrap(fileKey)
out, _ := i.Unwrap(stanzas)
fmt.Printf("== Private key ==\n")
fmt.Printf("Private key=%s\n", base64.StdEncoding.EncodeToString(private))
fmt.Printf("== Public key ==\n")
fmt.Printf("Public key=%s\n", base64.StdEncoding.EncodeToString(public))
fmt.Printf("\nOriginal key: %x\t ", fileKey)
fmt.Printf("\n\n== Wrapped key ==\n")
fmt.Printf("Type: %s, Args: %s\n", stanzas[0].Type, stanzas[0].Args)
fmt.Printf("Body %x\n", stanzas[0].Body)
fmt.Printf("\nUnwrapped key: %x\t ", out)
}

and a sample run is [here]:

== Private key ==
Private key=ki3DwJfKnfulIvUzCjEgCY3gzbV3mMhQlEXr6Di5UdtZdweNvHMJhMLWTkY1/jjGBzctPqixgfJZzTtK9XCm+Q==
== Public key ==
Public key=WXcHjbxzCYTC1k5GNf44xgc3LT6osYHyWc07SvVwpvk=
Original key: 628f1f4a57cd2178f545d5c8bbe0b6f9
== Wrapped key ==
Type: ssh-ed25519, Args: [Vd7a3w sahUTFfZ22ZNTYFY/ICIWA+VVY8105OWGDAiYexddDY]
Body 30caf1b8741bf2beb9cf5d2906307af0d7914e38b46d693cc5ed5f638b3efb39
Unwrapped key: 628f1f4a57cd2178f545d5c8bbe0b6f9

We can see that the public key is 32 bytes long, and the private key has 64 bytes. The private key is thus made up of both the actual private key and public key. We can see that the secret key is “28f1f4a57cd2178f545d5c8bbe0b6f9”.

RSA Key Wrapping

The Golang code for this is [here]:

package main
import (
"fmt"
"crypto/rand"
"crypto/rsa"
"filippo.io/age/agessh"
"golang.org/x/crypto/ssh"
)
func main() {
pk, _ := rsa.GenerateKey(rand.Reader, 2048)
pub, _ := ssh.NewPublicKey(&pk.PublicKey)
r, _ := agessh.NewRSARecipient(pub)
i, _ := agessh.NewRSAIdentity(pk)
fileKey := make([]byte, 16)
_, _ = rand.Read(fileKey)
stanzas, _ := r.Wrap(fileKey)
out, _ := i.Unwrap(stanzas)
fmt.Printf("== Public key ==\n")
fmt.Printf("N=%d\n", pk.PublicKey.N)
fmt.Printf("E=%d\n", pk.PublicKey.E)
fmt.Printf("== Private key ==\n")
fmt.Printf("D=%d\n", pk.D)
fmt.Printf("P=%d\n", pk.Primes[0])
fmt.Printf("Q=%d\n", pk.Primes[1])
fmt.Printf("\nOriginal key: %x\t ", fileKey)
fmt.Printf("\n== Wrapped key ==\n")
fmt.Printf("Type: %s\n", stanzas[0].Type)
fmt.Printf("Args: %s\n", stanzas[0].Args)
fmt.Printf("Body %x\n", stanzas[0].Body)
fmt.Printf("\nUnwrapped key: %x\t ", out)
}

and a sample run is [here]:

== Public key ==
N=28531907279526302505357058236815924177794272811962946574860258737147313736935638848458169971054537165665197103853480587838670040413994973250006494404291081682852882082347350850275572446885543584792319930628517096835215934932216507835253435872141195780586328200476641991506826773262771297675996914899607130954102250862441729521265611218332355624650457535410232357002700119275642446994520712937771585883957516531918297166009817251093061795912226184517189835135920452448863024292707260451735135539682469868569106927985191376881747738195906449355107484188714523321448370778486650606789692581004864844682626038644422389567
E=65537
== Private key ==
D=18425124431757818860059530016609297695234893479223886124498159364535279490586845710735106421028886307357408362135694423585275393295407102215340568780664466774214560866214574708719696920778948923441423380747670790122526340818183118613967620015880490224239354595122944937433082422113111782050498824059404199097078255330486123548625011460514333981789229340803486350373732630187786753999390081032995069437016087598017636760979485673812755217923573018220836194074843550636541086265700321462531935052395993057524701559332403258013098191222257663379754648458258274657384345698888220114805300113173094058527554973152422665969
P=169998462089613717489681272627456855583535964495483104887932436021368775816893779453063534063886270772894893289512168621113317731681216314879234408703354892874695388395040010948943448649626858149049949196082103295074501044556072680452891071130327957184252115517633230799390082737056797893906310766481633385723
Q=167836267039203394007077431935986473699955848400735619856289459526260333035336216111724783881558809368699651208752443946725962609737904513200666506932470531502077631394212734884154679481878394267343070446473105748259578432259627245464784807841032174893375125255937837328688893112294906294055924688648533433229

Original key: 8a1fb51adc8bd8e97197c1282991da90
== Wrapped key ==
Type: ssh-rsa
Args: [oPiyOg]
Body 57289728cc5cc9aa52973c33bb550480b6457af3026baf7e478f0257480ca5086087d81e1d37987b4efcbc3e9e7eb32af57f6f0f73f945bfffe2cb10f2e49d0f13ce76ee0b0ac865a95352515b147468e0f1bca99d816c453d995b10bd8eeaf1b55e38a7b0bc458bcb3592aa6adfe31cf69b5959bac2100f79d05b4a058e4ece101466512df1476c36ae9012f66c1b766f5d8518385c727ff72d658ebd01dfd2808727c98e0a28c4829fedb6fb1d33591aad3d03e0ebacb8a79fa9a209a60f8b036b767438f291d01fcf5eaefb2e3e62f7f8e14b30f58f7d5c3bfac10121d76b35484064978e599cb498270ef850c328741cef8c65579a80468aed9dea46c640
Unwrapped key: 8a1fb51adc8bd8e97197c1282991da90

This shows the encryption key (e,N) and the decryption key (d,N).

--

--

Prof Bill Buchanan OBE FRSE
ASecuritySite: When Bob Met Alice

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.