├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── main.go └── test-key.asc /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [pinpox, felschr] 2 | buy_me_a_coffee: pinpox 3 | custom: ['https://github.com/pinpox/pgp2ssh#support--donations'] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pgp2ssh 2 | result 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pablo Ovelleiro Corral 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pgp2ssh 2 | 3 | Derive private ed25519 SSH key from private PGP key. 4 | 5 | GPG itself only supports exporting _public_ SSH keys and other tools don't work for ed25519 keys. 6 | 7 | ##### Notes: 8 | 9 | - A tool exists to do this for RSA keys: [openpgp2ssh](https://manpages.ubuntu.com/manpages/xenial/man1/openpgp2ssh.1.html) but it does not seem to support `ed25519` keys 10 | - Work on `gnupg` was started for this feature, but never finished see this 11 | issue and commit: https://dev.gnupg.org/T6647 12 | 13 | ## Instructions 14 | 15 | First you need to export your PGP key from GPG: 16 | 17 | ```sh 18 | ❯ gpg2 --export-secret-keys --armor test@test.test >priv-gpg 19 | ``` 20 | 21 | Then identify the public SSH key that was used to encrypt your secret. 22 | You can search for your GitHub username in: https://fluence-dao.s3.eu-west-1.amazonaws.com/metadata.json 23 | 24 | If you have multiple subkeys, usually it is the authenticate key highlighted with `[A]` in the output of: 25 | 26 | ```sh 27 | ❯ gpg --list-secret-keys --with-keygrip 28 | ``` 29 | 30 | ### Derive private SSH key 31 | 32 | ```sh 33 | ❯ go build 34 | ❯ ./pgp2ssh 35 | ``` 36 | 37 | **Nix/NixOS Users** 38 | 39 | A flake is provided for Nix users. Just use `nix run` instead of building and 40 | running manually. 41 | 42 | It'll ask you for the path to your private PGP key, followed by choosing the key/subkey and if your PGP key is encrypted it'll ask for the passphrase. 43 | 44 | In the output, verify that the public SSH key printed matches the one in `metadata.json`. 45 | If it matches, the last part of the output it will print the matching private SSH key. 46 | You can save the key to a file and use how you want. 47 | 48 | ### Example: Decrypt age files 49 | 50 | If you want to decrypt a file that was encryptd by `age` with your public SSH key, you can just use `age` as normal to decrypt the file using the SSH private key that we've got in the previous step: 51 | 52 | ```sh 53 | ❯ age --decrypt --identity ./ssh-secret-key --output decrypted ./testfile.txt.age 54 | ``` 55 | 56 | ## Troubleshooting 57 | 58 | If the conversion fails with the error: 59 | 60 | ``` 61 | 2024/03/27 22:09:09 openpgp: invalid data: user ID signature with wrong type 62 | ``` 63 | 64 | You might be missing the private key of your subkeys. When running `gpg -K` you 65 | should **NOT** see a `>` infront of the keys like this: 66 | 67 | ``` 68 | ssb> ed25519/0xB68746238E59B548 2018-07-09 [S] [expires: 2026-01-02] 69 | Keygrip = C89E5AABCBF7142DBC26E68FB3121DE12DCBF4FF 70 | ssb> cv25519/0x65CD5E0200C56C17 2018-07-09 [E] [expires: 2026-01-02] 71 | Keygrip = 867EA9F6ADBEBE18ED98253B884F53CBD53C526B 72 | ssb> ed25519/0xF36CF32DF9B09855 2018-07-09 [A] [expires: 2026-01-02] 73 | Keygrip = 553D56865642B05AB3C5B62DC68795691702B960 74 | ``` 75 | The `>` (corner of a card) indicates, that the private part is on a smart card 76 | or not available. This may also be caused by expired keys. For possible 77 | solutions see https://github.com/pinpox/pgp2ssh/issues/6 78 | 79 | 80 | ### Support & Donations 81 | 82 | This project was built with lots of headaches by [pinpox](https://github.com/pinpox/) & [felschr](https://github.com/felschr/). If you need help, feel free to contact us. 83 | 84 | And if you want to thank us, you can send us any crypto or token to our Ethereum / Polygon wallets 😊: 85 | pinpox: `0xde031f16976AFcaC613087B6213Eb521F63d3A49` 86 | felschr: `0xD66753D737603E18018281E298Df86DE402d313E` 87 | 88 | 89 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1711523803, 6 | "narHash": "sha256-UKcYiHWHQynzj6CN/vTcix4yd1eCu1uFdsuarupdCQQ=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "2726f127c15a4cc9810843b96cad73c7eb39e443", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixos-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "root": { 19 | "inputs": { 20 | "nixpkgs": "nixpkgs" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Convert GPG/PGP Keys to SSH private keys"; 3 | 4 | # Nixpkgs / NixOS version to use. 5 | inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; 6 | 7 | outputs = { self, nixpkgs }: 8 | let 9 | 10 | # to work with older version of flakes 11 | lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101"; 12 | 13 | # Generate a user-friendly version number. 14 | version = builtins.substring 0 8 lastModifiedDate; 15 | 16 | # System types to support. 17 | supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; 18 | 19 | # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. 20 | forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 21 | 22 | # Nixpkgs instantiated for supported system types. 23 | nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); 24 | 25 | in 26 | { 27 | 28 | # Provide some binary packages for selected system types. 29 | packages = forAllSystems (system: 30 | let 31 | pkgs = nixpkgsFor.${system}; 32 | in 33 | { 34 | pgp2ssh = pkgs.buildGoModule { 35 | pname = "pgp2ssh"; 36 | inherit version; 37 | src = ./.; 38 | vendorHash = "sha256-O4AeSfdJxSGnWwRkNnAQMnOZE+Auy+3BIjncG/PK5EE="; 39 | }; 40 | }); 41 | 42 | # The default package for 'nix build' and 'nix run' 43 | defaultPackage = forAllSystems (system: self.packages.${system}.pgp2ssh); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module pgp2ssh 2 | 3 | go 1.21.8 4 | 5 | require ( 6 | github.com/ProtonMail/go-crypto v1.0.0 7 | golang.org/x/crypto v0.20.0 8 | golang.org/x/term v0.18.0 9 | ) 10 | 11 | require ( 12 | github.com/cloudflare/circl v1.3.7 // indirect 13 | golang.org/x/sys v0.18.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= 2 | github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= 3 | github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= 4 | github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 5 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 6 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 7 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 8 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 9 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 10 | golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 11 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 12 | golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= 13 | golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= 14 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 15 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 16 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 17 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 18 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 19 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 20 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 21 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 22 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 23 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 35 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 36 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 37 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 38 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 39 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 40 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 41 | golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= 42 | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 44 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 45 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 46 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 47 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 48 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 51 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 52 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 53 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/pem" 5 | "fmt" 6 | "log" 7 | "os" 8 | "syscall" 9 | 10 | "github.com/ProtonMail/go-crypto/openpgp" 11 | "github.com/ProtonMail/go-crypto/openpgp/armor" 12 | "github.com/ProtonMail/go-crypto/openpgp/eddsa" 13 | "github.com/ProtonMail/go-crypto/openpgp/packet" 14 | 15 | "crypto/ed25519" 16 | "crypto/rsa" 17 | "errors" 18 | "reflect" 19 | 20 | "golang.org/x/crypto/ssh" 21 | "golang.org/x/term" 22 | ) 23 | 24 | func readEntity(keypath string) (*openpgp.Entity, error) { 25 | f, err := os.Open(keypath) 26 | if err != nil { 27 | log.Println("Error opening file") 28 | return nil, err 29 | } 30 | defer f.Close() 31 | block, err := armor.Decode(f) 32 | if err != nil { 33 | log.Println("decoding") 34 | return nil, err 35 | } 36 | return openpgp.ReadEntity(packet.NewReader(block.Body)) 37 | } 38 | 39 | var ( 40 | UnsupportedKeyType = errors.New("only ed25519 and rsa keys are supported") 41 | ) 42 | 43 | func getEDDSAKey(castkey *eddsa.PrivateKey) []byte { 44 | log.Println("public key type:", reflect.TypeOf(castkey.PublicKey)) 45 | var pubkey ed25519.PublicKey = castkey.PublicKey.X 46 | 47 | sshPub, err := ssh.NewPublicKey(pubkey) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | log.Println("public SSH key:\n" + string(ssh.MarshalAuthorizedKey(sshPub))) 52 | 53 | var privkey = ed25519.NewKeyFromSeed(castkey.D) 54 | 55 | privPem, err := ssh.MarshalPrivateKey(&privkey, "") 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | return pem.EncodeToMemory(privPem) 60 | } 61 | 62 | func getRSAKey(castkey *rsa.PrivateKey) []byte { 63 | 64 | log.Println("public key type:", reflect.TypeOf(castkey.PublicKey)) 65 | var pubkey rsa.PublicKey = castkey.PublicKey 66 | 67 | sshPub, err := ssh.NewPublicKey(&pubkey) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | log.Println("public SSH key:\n" + string(ssh.MarshalAuthorizedKey(sshPub))) 72 | 73 | // var privkey = ed25519.NewKeyFromSeed(castkey.D) 74 | 75 | privPem, err := ssh.MarshalPrivateKey(castkey, "") 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | return pem.EncodeToMemory(privPem) 80 | } 81 | 82 | func main() { 83 | var keyfile string 84 | log.Println("Enter path to private PGP key (default: ./priv.asc):") 85 | _, err := fmt.Scanf("%s", &keyfile) 86 | if err != nil && err.Error() == "unexpected newline" { 87 | keyfile = "./priv.asc" 88 | } else if err != nil { 89 | log.Fatal(err) 90 | } 91 | 92 | e, err := readEntity(keyfile) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | 97 | log.Println("Keys:") 98 | log.Println("[0]", e.PrimaryKey.KeyIdString()+" (primary)") 99 | for i := 0; i < len(e.Subkeys); i++ { 100 | log.Println(fmt.Sprintf("[%d]", i+1), e.Subkeys[i].PrivateKey.KeyIdString()+" (subkey)") 101 | } 102 | 103 | log.Println("Choose key by index (default: 0):") 104 | 105 | var keyIndex int 106 | _, err = fmt.Scanf("%d", &keyIndex) 107 | if err != nil && err.Error() == "unexpected newline" { 108 | keyIndex = 0 109 | } else if err != nil { 110 | log.Fatal(err) 111 | } 112 | 113 | var targetKey *packet.PrivateKey 114 | if keyIndex == 0 { 115 | log.Println(fmt.Sprintf("Continuing with key [%d]", keyIndex), e.PrimaryKey.KeyIdString()) 116 | targetKey = e.PrivateKey 117 | } else if keyIndex > 0 { 118 | var subkey = e.Subkeys[keyIndex-1] 119 | log.Println(fmt.Sprintf("Continuing with key [%d]", keyIndex), subkey.PrivateKey.KeyIdString()) 120 | targetKey = subkey.PrivateKey 121 | } else { 122 | log.Fatal("Invalid key index") 123 | } 124 | 125 | if targetKey.Encrypted { 126 | log.Println("Please enter passphrase to decrypt PGP key:") 127 | bytePassphrase, err := term.ReadPassword(int(syscall.Stdin)) 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | targetKey.Decrypt(bytePassphrase) 132 | } 133 | log.Println("private key type:", reflect.TypeOf(targetKey.PrivateKey)) 134 | castkey_eddsa, ok_eddsa := targetKey.PrivateKey.(*eddsa.PrivateKey) 135 | if ok_eddsa { 136 | privateKeyPem := getEDDSAKey(castkey_eddsa) 137 | log.Println("Private SSH key:\n" + string(privateKeyPem)) 138 | return 139 | } 140 | castkey_rsa, ok_rsa := targetKey.PrivateKey.(*rsa.PrivateKey) 141 | if ok_rsa { 142 | privateKeyPem := getRSAKey(castkey_rsa) 143 | log.Println("Private SSH key:\n" + string(privateKeyPem)) 144 | return 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /test-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | lFgEZgGUQBYJKwYBBAHaRw8BAQdAMKqz0/asx4jNcVll94PNWK0GhUbJn2vvLWZu 4 | xos9Oz4AAQDC2iB2GpUXbtJMPyn7x9T+jZDP5s0DFR9va2V/upKfchBytBt0ZXN0 5 | ICh0ZXN0KSA8dGVzdEB0ZXN0LmNvbT6IkwQTFgoAOxYhBJ/k1IS2nbn1x6ogjnYY 6 | jPMHF7VOBQJmAZRAAhsBBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEHYY 7 | jPMHF7VOvpMBAJJi5eOZVErrSHyvSfic4LufaR7fUd89L9eynHSx5Cp5AQCZogU5 8 | HSv+paIFol1TAE6+NmmowMB1AfoPRfgmJkEvDJxYBGYBlSMWCSsGAQQB2kcPAQEH 9 | QInBN59L8q33LhfUuxJCR/DSsoQCJA0lkXnxrm9R6q+sAAD+I5FMI4vPF90QuU6T 10 | C80aGfQ6etYMQc1iUAPEAlB0LzQNXojvBBgWCgAgFiEEn+TUhLadufXHqiCOdhiM 11 | 8wcXtU4FAmYBlSMCGwIAgQkQdhiM8wcXtU52IAQZFgoAHRYhBCdPmI8t9EpgeCgC 12 | WfMLK/n7oQXBBQJmAZUjAAoJEPMLK/n7oQXBN2gBAN8oHUADMQ8WDduc2Elhjiaw 13 | aQ19ygb3rpcHrNW1rlWeAQCTVM4oogoecqYGDthvHV+5wk6nYhkpuLkDKm1hgTep 14 | C3Q+AP9mE3YjRNcpeGZVceqsiPcdLUEfbMTqAYfAOAmn9FRUpwEA6SXje0n6umix 15 | ArKnOkPfH9ctJxMGdJmti8Oo8y0ovAScXQRmAZUwEgorBgEEAZdVAQUBAQdARxBe 16 | 90CUDkw7l0PHidsEueTLijE9eXZ01JlHLDoZx2ADAQgHAAD/egF2H4MKU6pEU2ar 17 | uuKfpa+TJg1I6WlhQoraoWgJ3nAPl4h4BBgWCgAgFiEEn+TUhLadufXHqiCOdhiM 18 | 8wcXtU4FAmYBlTACGwwACgkQdhiM8wcXtU5H2wD/Q4etKCvgr4WTJ/9iON+Ptwm/ 19 | P/KJrtSi2QKB0ZlCJFMBAOFkBbjsFDYxfWyX1uu4mQyBHB9dFEKExlmQMaWbgF0C 20 | nFgEZgGVShYJKwYBBAHaRw8BAQdAJi8pcZwbhrX21NPRHJxuf4PSv9iUqW0sn11j 21 | aBH8WPEAAP4wjI46SYrEwGL8kOAfAsVmsYW1Cwo4uKL44Nj7qolUUhIJiHgEGBYK 22 | ACAWIQSf5NSEtp259ceqII52GIzzBxe1TgUCZgGVSgIbIAAKCRB2GIzzBxe1TpHO 23 | AP9rBhTpMlh89hXBXl74V3dW6ACI79udkSbl8APYP2KUHwEAj5+PDPqO1kTNT4/o 24 | D3FU4DR8FKcXGRCSOnDCEIA+nA0= 25 | =24OF 26 | -----END PGP PRIVATE KEY BLOCK----- 27 | --------------------------------------------------------------------------------