├── README.md ├── LICENSE └── edkey.go /README.md: -------------------------------------------------------------------------------- 1 | # edkey 2 | edkey allows you to marshal/write ED25519 private keys in the OpenSSH private key format 3 | 4 | ## Example 5 | ```go 6 | package main 7 | 8 | import ( 9 | "crypto/rand" 10 | "encoding/pem" 11 | "io/ioutil" 12 | "github.com/mikesmitty/edkey" 13 | "golang.org/x/crypto/ed25519" 14 | "golang.org/x/crypto/ssh" 15 | ) 16 | 17 | func main() { 18 | // Generate a new private/public keypair for OpenSSH 19 | pubKey, privKey, _ := ed25519.GenerateKey(rand.Reader) 20 | publicKey, _ := ssh.NewPublicKey(pubKey) 21 | 22 | pemKey := &pem.Block{ 23 | Type: "OPENSSH PRIVATE KEY", 24 | Bytes: edkey.MarshalED25519PrivateKey(privKey), 25 | } 26 | privateKey := pem.EncodeToMemory(pemKey) 27 | authorizedKey := ssh.MarshalAuthorizedKey(publicKey) 28 | 29 | _ = ioutil.WriteFile("id_ed25519", privateKey, 0600) 30 | _ = ioutil.WriteFile("id_ed25519.pub", authorizedKey, 0644) 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michael Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /edkey.go: -------------------------------------------------------------------------------- 1 | package edkey 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "golang.org/x/crypto/ed25519" 7 | "golang.org/x/crypto/ssh" 8 | ) 9 | 10 | /* Writes ed25519 private keys into the new OpenSSH private key format. 11 | I have no idea why this isn't implemented anywhere yet, you can do seemingly 12 | everything except write it to disk in the OpenSSH private key format. */ 13 | func MarshalED25519PrivateKey(key ed25519.PrivateKey) []byte { 14 | // Add our key header (followed by a null byte) 15 | magic := append([]byte("openssh-key-v1"), 0) 16 | 17 | var w struct { 18 | CipherName string 19 | KdfName string 20 | KdfOpts string 21 | NumKeys uint32 22 | PubKey []byte 23 | PrivKeyBlock []byte 24 | } 25 | 26 | // Fill out the private key fields 27 | pk1 := struct { 28 | Check1 uint32 29 | Check2 uint32 30 | Keytype string 31 | Pub []byte 32 | Priv []byte 33 | Comment string 34 | Pad []byte `ssh:"rest"` 35 | }{} 36 | 37 | // Set our check ints 38 | ci := rand.Uint32() 39 | pk1.Check1 = ci 40 | pk1.Check2 = ci 41 | 42 | // Set our key type 43 | pk1.Keytype = ssh.KeyAlgoED25519 44 | 45 | // Add the pubkey to the optionally-encrypted block 46 | pk, ok := key.Public().(ed25519.PublicKey) 47 | if !ok { 48 | //fmt.Fprintln(os.Stderr, "ed25519.PublicKey type assertion failed on an ed25519 public key. This should never ever happen.") 49 | return nil 50 | } 51 | pubKey := []byte(pk) 52 | pk1.Pub = pubKey 53 | 54 | // Add our private key 55 | pk1.Priv = []byte(key) 56 | 57 | // Might be useful to put something in here at some point 58 | pk1.Comment = "" 59 | 60 | // Add some padding to match the encryption block size within PrivKeyBlock (without Pad field) 61 | // 8 doesn't match the documentation, but that's what ssh-keygen uses for unencrypted keys. *shrug* 62 | bs := 8 63 | blockLen := len(ssh.Marshal(pk1)) 64 | padLen := (bs - (blockLen % bs)) % bs 65 | pk1.Pad = make([]byte, padLen) 66 | 67 | // Padding is a sequence of bytes like: 1, 2, 3... 68 | for i := 0; i < padLen; i++ { 69 | pk1.Pad[i] = byte(i + 1) 70 | } 71 | 72 | // Generate the pubkey prefix "\0\0\0\nssh-ed25519\0\0\0 " 73 | prefix := []byte{0x0, 0x0, 0x0, 0x0b} 74 | prefix = append(prefix, []byte(ssh.KeyAlgoED25519)...) 75 | prefix = append(prefix, []byte{0x0, 0x0, 0x0, 0x20}...) 76 | 77 | // Only going to support unencrypted keys for now 78 | w.CipherName = "none" 79 | w.KdfName = "none" 80 | w.KdfOpts = "" 81 | w.NumKeys = 1 82 | w.PubKey = append(prefix, pubKey...) 83 | w.PrivKeyBlock = ssh.Marshal(pk1) 84 | 85 | magic = append(magic, ssh.Marshal(w)...) 86 | 87 | return magic 88 | } 89 | --------------------------------------------------------------------------------