Skip to content

Commit

Permalink
feat: export subkeys in pseudorandom order
Browse files Browse the repository at this point in the history
Fixes #42.

When GPG exports subkeys, it uses the same
order which they were imported in. If
mnemonikey subkeys are always imported in EAS
order, this can be used to identify PGP keys
generated by mnemonikey. We now shuffle subkeys
pseudorandomly to avoid identification while
maintaining deterministic output.
  • Loading branch information
kklash committed Dec 7, 2023
1 parent 47344ad commit aae192e
Showing 1 changed file with 27 additions and 3 deletions.
30 changes: 27 additions & 3 deletions pgp/key_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package pgp

import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"math/rand"
)

// KeySet represents a full set of PGP keys, with an associated user identifier.
Expand Down Expand Up @@ -92,13 +95,16 @@ func (keySet *KeySet) encodeSigningSubkeyPackets(password []byte) ([]byte, error
func (keySet *KeySet) encodeAllSubkeyPackets(password []byte) ([]byte, error) {
buf := new(bytes.Buffer)

// Ensure packets are always ordered consistently for the same master key.
packetGroups := [][]byte{[]byte{}, []byte{}, []byte{}}

// Encryption subkey
if keySet.EncryptionSubkey != nil {
subkeyPackets, err := keySet.encodeEncryptionSubkeyPackets(password)
if err != nil {
return nil, err
}
buf.Write(subkeyPackets)
packetGroups[0] = subkeyPackets
}

// Authentication subkey
Expand All @@ -107,7 +113,7 @@ func (keySet *KeySet) encodeAllSubkeyPackets(password []byte) ([]byte, error) {
if err != nil {
return nil, err
}
buf.Write(subkeyPackets)
packetGroups[1] = subkeyPackets
}

// Signing subkey
Expand All @@ -116,7 +122,25 @@ func (keySet *KeySet) encodeAllSubkeyPackets(password []byte) ([]byte, error) {
if err != nil {
return nil, err
}
buf.Write(subkeyPackets)
packetGroups[2] = subkeyPackets
}

// Pseudo-randomly shuffle the subkeys based on a hash of the master private key.
// Not required as part of the spec, but good for privacy. Helps
// to prevent mnemonikeys from being recognizable based on subkey order.
fingerprint := sha256.Sum256(keySet.MasterKey.Private)
lastBits := binary.BigEndian.Uint64(fingerprint[:8])
seed := int64(lastBits & 0x7FFFFFFFFFFFFFFF)
if uint64(seed) != lastBits {
seed *= -1
}
rng := rand.New(rand.NewSource(int64(seed)))
rng.Shuffle(len(packetGroups), func(i, j int) {
packetGroups[i], packetGroups[j] = packetGroups[j], packetGroups[i]
})

for _, packetGroup := range packetGroups {
buf.Write(packetGroup)
}

return buf.Bytes(), nil
Expand Down

0 comments on commit aae192e

Please sign in to comment.