krypto431

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 3, 2024 License: MIT Imports: 33 Imported by: 0

README

Krypto431

Project Krypto431 is a simple OTP (One Time Pad) based on the DIANA cryptosystem utilizing a reciprocal table with single-use keys. Krypto431 is both a CLI (command-line interface) tool and a traditional pen-and-paper cipher. The tool is provided for Linux, BSD-derivatives and Windows.

Purpose

Krypto431 was designed to ease and facility passing sensitive information over any mode of communication, with or without electronic equipment. Although the system can be managed without a computer, the tool is an aid to generate, manage, and distribute keys to multiple stations and also simplify the message exchange (encoding, enciphering, deciphering, and decoding).

Naturally, the intended particular purpose of this type of cipher is to encrypt messages in a contested (electronically and otherwise) hostile environment. Stations can be fully within enemy lines (civilian resistance movement) as well as beyond enemy lines (armed resistance, remote reconnaissance, etc). Another obvious purpose would be to encrypt sensitive information (such as casualties, names, addresses for example) during emergency communication.

Although primarily not intended for amateur radio, we are allowed to exchange encrypted messages over ham radio within Sweden as long as our call-signs can be decoded. This allows us to practice sending and receiving old-style radiogram formatted crypto-group messages over telegraphy as well as voice, RTTY or a more modern data mode of choice.

Status

Current version (v0.1.x) is a proof-of-concept and a pre-release to demonstrate the idea. Version 0.2 will include (aside from bug-fixes) printable instructions how to encipher, decipher, distribute keys, generating a key with dice. Support for binary file transfer will be included in v0.3.

Installation

Binaries are provided in the release tarball (zip) for various operating systems and architectures. If you have Go installed, you can download and build the CLI tool with this oneliner...

go install github.com/sa6mwa/krypto431/cmd/krypto431@latest

Examples

Read ahead how to encipher/decipher by hand and further information. Crude examples of current version...

$ ./krypto431 messages -n
Enter decryption key: 
Message header as well as text body is entered as a radiogram according to the
following simplified ACP 124 radiotelegraph message format:
TO1 TO2 TO3 DE FROM 012345 = Hello, this is the body of the message = K
DE FROM This is the shortest form.
TO DE FROM 012345ZDEC22 COL 3 = ABCDE FGHIJ KLMNO = K
TO DE FROM 012345 == TO2 TO3 == COL 2 = Hello world K
TO DE FROM 012345 C = This is a broadcast message. +
DE FROM 012345 4 = ABCDE FGHIJ KLMNO QRSTU = K
*) TO is(/are) the call-sign(s) of the recipient(s).
   FROM is your call-sign.
   012345 is a Date-Time Group (day hour minute, full format DDHHMMZmmmYY).
? Enter message as radiogram (your call is SA6MWA) [Enter 2 empty lines to finish]
qj de sa6mwa Hello world, this is a short message. = K
________________________________________________________________________________
082223AJAN23             TO: QJ
ID: C7X9          FROM (DE): SA6MWA
=TEXT=
Hello world, this is a short message.
=CIPHER=
PHPSN DLAUC VESIM SEIYG NBOBP CIAMS JHZIA BGBGG CIYAU CKMNM
=TRAFFIC=EXAMPLE=
QJ DE SA6MWA 082223AJAN23 10 = PHPSN DLAUC VESIM SEIYG NBOBP CIAMS JHZIA BGBGG
CIYAU CKMNM = K

Saved message C7X9 in /home/sa6mwa/.krypto431.gob.


$ krypto431 keys -k qj -o keysToGiveToQJ.pdf
...

$ ./krypto431 keys -k qj -E keysToQJ.gob
Enter decryption key: 
Exported 10 keys from /home/sa6mwa/.krypto431.gob to keysToQJ.gob (change PFK/salt with the pfk command).
Initialization

Krypto431 uses (per default) an encrypted GOB (Go Binary) file under your home folder named .krypto431.gob. See krypto431 -h and krypto431 pfk -h for full information on how to manage these key and message stores.

# For help: krypto431 init -h

$ krypto431 init
? Enter your call-sign: sa6mwa
? Enter number of initial keys to generate: 10
? Enter keepers of the initial keys (leave empty for anonymous): qj
? Enter expiry date as a Date-Time Group or empty for default: 101500ZFEB23
? Choose length of keys: 350
? Choose group size: 5
? Overwrite /home/sa6mwa/.krypto431.gob? Yes
Enter encryption key: 
Denied: insecure password, try including more special characters or using a longer password (42<60)
Enter encryption key: 
OK: Password entropy is 97
Enter encryption key: (repeat) 
Saved /home/sa6mwa/.krypto431.gob

Case

One Time Pad (OTP) ciphers are pretty simple and straight forward, but in order to be able to communicate, encrypt and decrypt messages you need to agree on a format, consider key distribution and provide clear instructions how to use such a system. If there ever is a need to pass sensitive information in a short message format over an unsecure channel (for example over radio telegraphy or radio telephony), there are no open civilian solutions readily available. Krypto431 was realized to provide a standard and a set of tools for effectively passing encrypted messages that can - if necessary - be deciphered (and enciphered) without an electronic or a mechanical device.

431 is the sum of all ascii characters in my amateur radio callsign SA6MWA. As the name suggests, the concept is to add or subtract in order to encrypt or decrypt a text message. The system is designed to be easy to use without any electronic equipment, just pen and paper.

How

Traditionally, ciphertext has consisted of groups of 5 letters where the first group identifies the key. When sending such a ciphertext (in for example military radio communication) you indicate group count - how many groups of 5 letters there are to be sent/received. Krypto431 employs the same concept. The first group consists of 5 letters that identify which key was used to encrypt it and - since it's symmetric - which key to use for decrypting the ciphertext. The remaining ciphertext is organized into groups of 5 letters.

When encrypting, you add the numerical representation of the letter (for example A=0) with the randomly generated number from the key. If the number is 26 or above, you wrap it starting from 0 (modulo 26).

When decrypting, you subtract the random number in the key from the ciphered numerical representation of the letter. If the number is negative, you wrap it around starting from 26 (or, for example 4 minus number from the key, e.g 18 = (26+4-18)%26 = 12 = L).

If the encryptor has enciphered the whole message and is left with a final group of less than 5 letters, the encryptor should add Z to the plaintext to fill up any remaining group as Z will be used as an operator character that changes the character table to and from an alternate table. Filling up with Z just changes the table back and forth without adding any real characters to the output.

DIANA APPROACH

Krypto431 will utilize the NSA cipher codenamed DIANA mainly used by US Special Forces during the Vietnam war.

The US DIANA cipher uses a trigraph designed so that you use the same column for both encryption and decryption. This is achieved by having an alphabeth in reverse (Z to A) of the normal alphabeth sequence (A to Z).

Read more here http://users.telenet.be/d.rijmenants/en/onetimepad.htm.

The trigraph...

0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z
-----------------------------------------------------------------------------
Az Ay Ax Aw Av Au At As Ar Aq Ap Ao An Am Al Ak Aj Ai Ah Ag Af Ae Ad Ac Ab Aa
By Bx Bw Bv Bu Bt Bs Br Bq Bp Bo Bn Bm Bl Bk Bj Bi Bh Bg Bf Be Bd Bc Bb Ba Bz
Cx Cw Cv Cu Ct Cs Cr Cq Cp Co Cn Cm Cl Ck Cj Ci Ch Cg Cf Ce Cd Cc Cb Ca Cz Cy
Dw Dv Du Dt Ds Dr Dq Dp Do Dn Dm Dl Dk Dj Di Dh Dg Df De Dd Dc Db Da Dz Dy Dx
Ev Eu Et Es Er Eq Ep Eo En Em El Ek Ej Ei Eh Eg Ef Ee Ed Ec Eb Ea Ez Ey Ex Ew
Fu Ft Fs Fr Fq Fp Fo Fn Fm Fl Fk Fj Fi Fh Fg Ff Fe Fd Fc Fb Fa Fz Fy Fx Fw Fv
Gt Gs Gr Gq Gp Go Gn Gm Gl Gk Gj Gi Gh Gg Gf Ge Gd Gc Gb Ga Gz Gy Gx Gw Gv Gu
Hs Hr Hq Hp Ho Hn Hm Hl Hk Hj Hi Hh Hg Hf He Hd Hc Hb Ha Hz Hy Hx Hw Hv Hu Ht
Ir Iq Ip Io In Im Il Ik Ij Ii Ih Ig If Ie Id Ic Ib Ia Iz Iy Ix Iw Iv Iu It Is
Jq Jp Jo Jn Jm Jl Jk Jj Ji Jh Jg Jf Je Jd Jc Jb Ja Jz Jy Jx Jw Jv Ju Jt Js Jr
Kp Ko Kn Km Kl Kk Kj Ki Kh Kg Kf Ke Kd Kc Kb Ka Kz Ky Kx Kw Kv Ku Kt Ks Kr Kq
Lo Ln Lm Ll Lk Lj Li Lh Lg Lf Le Ld Lc Lb La Lz Ly Lx Lw Lv Lu Lt Ls Lr Lq Lp
Mn Mm Ml Mk Mj Mi Mh Mg Mf Me Md Mc Mb Ma Mz My Mx Mw Mv Mu Mt Ms Mr Mq Mp Mo
Nm Nl Nk Nj Ni Nh Ng Nf Ne Nd Nc Nb Na Nz Ny Nx Nw Nv Nu Nt Ns Nr Nq Np No Nn
Ol Ok Oj Oi Oh Og Of Oe Od Oc Ob Oa Oz Oy Ox Ow Ov Ou Ot Os Or Oq Op Oo On Om
Pk Pj Pi Ph Pg Pf Pe Pd Pc Pb Pa Pz Py Px Pw Pv Pu Pt Ps Pr Pq Pp Po Pn Pm Pl
Qj Qi Qh Qg Qf Qe Qd Qc Qb Qa Qz Qy Qx Qw Qv Qu Qt Qs Qr Qq Qp Qo Qn Qm Ql Qk
Ri Rh Rg Rf Re Rd Rc Rb Ra Rz Ry Rx Rw Rv Ru Rt Rs Rr Rq Rp Ro Rn Rm Rl Rk Rj
Sh Sg Sf Se Sd Sc Sb Sa Sz Sy Sx Sw Sv Su St Ss Sr Sq Sp So Sn Sm Sl Sk Sj Si
Tg Tf Te Td Tc Tb Ta Tz Ty Tx Tw Tv Tu Tt Ts Tr Tq Tp To Tn Tm Tl Tk Tj Ti Th
Uf Ue Ud Uc Ub Ua Uz Uy Ux Uw Uv Uu Ut Us Ur Uq Up Uo Un Um Ul Uk Uj Ui Uh Ug
Ve Vd Vc Vb Va Vz Vy Vx Vw Vv Vu Vt Vs Vr Vq Vp Vo Vn Vm Vl Vk Vj Vi Vh Vg Vf
Wd Wc Wb Wa Wz Wy Wx Ww Wv Wu Wt Ws Wr Wq Wp Wo Wn Wm Wl Wk Wj Wi Wh Wg Wf We
Xc Xb Xa Xz Xy Xx Xw Xv Xu Xt Xs Xr Xq Xp Xo Xn Xm Xl Xk Xj Xi Xh Xg Xf Xe Xd
Yb Ya Yz Yy Yx Yw Yv Yu Yt Ys Yr Yq Yp Yo Yn Ym Yl Yk Yj Yi Yh Yg Yf Ye Yd Yc
Za Zz Zy Zx Zw Zv Zu Zt Zs Zr Zq Zp Zo Zn Zm Zl Zk Zj Zi Zh Zg Zf Ze Zd Zc Zb

   Message: HELLO WORLD
Random key: YNQCI CPWZH
Ciphertext: UIYMD BWMPP

ENCRYPT
Take the letter from Message (first is H), find it in the first row above (the
row with A to Z in capital letters). You now have your column. Find the key
letter i upper case (Y) in the column and you see the bigram "Yu". The letter
to write as ciphertext is the one in lower case (u).

DECRYPT
Take the letter from Ciphertext (first is U), find it in the first row above
(the row with A to Z in capital letters). You now have your column.  Find the
key letter in upper case (Y) in the column and you see the bigram "Yh". The
letter to write as plaintext is the one in lower case (h).

This is very convenient - encryption and decryption uses exactly the same
procedure: plaintext with key for encrypt, ciphertext with key for decrypt -
same row and column procedure.

Forgot whether the letter of the message or the key should be used as row or
column? No problem! The really cool thing is that it does not matter if you use
the letter from the message or the letter from the key as row (or column). You
can mix them up, and it's OK, the result will be the same - for both encryption
and decryption.

Unlike with simple modulo 26, a zero-key encryption (key consists of only A)
does not work, instead each letter has to be coded aaccording to both forward
and reverse position of the text and we end up with...

   Message: HELLO WORLD
       Key: LRDDX HXRDT
CipherText: HELLO WORLD


Another arrangement of the trigraph...

  A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
 ----------------------------------------------------
A A A A A A A A A A A A A A A A A A A A A A A A A A A
  Z Y X W V U T S R Q P O N M L K J I H G F E D C B A

B B B B B B B B B B B B B B B B B B B B B B B B B B B
  Y X W V U T S R Q P O N M L K J I H G F E D C B A Z

C C C C C C C C C C C C C C C C C C C C C C C C C C C
  X W V U T S R Q P O N M L K J I H G F E D C B A Z Y

D D D D D D D D D D D D D D D D D D D D D D D D D D D
  W V U T S R Q P O N M L K J I H G F E D C B A Z Y X

E E E E E E E E E E E E E E E E E E E E E E E E E E E
  V U T S R Q P O N M L K J I H G F E D C B A Z Y X W

F F F F F F F F F F F F F F F F F F F F F F F F F F F
  U T S R Q P O N M L K J I H G F E D C B A Z Y X W V

G G G G G G G G G G G G G G G G G G G G G G G G G G G
  T S R Q P O N M L K J I H G F E D C B A Z Y X W V U

H H H H H H H H H H H H H H H H H H H H H H H H H H H
  S R Q P O N M L K J I H G F E D C B A Z Y X W V U T

I I I I I I I I I I I I I I I I I I I I I I I I I I I
  R Q P O N M L K J I H G F E D C B A Z Y X W V U T S

J J J J J J J J J J J J J J J J J J J J J J J J J J J
  Q P O N M L K J I H G F E D C B A Z Y X W V U T S R

K K K K K K K K K K K K K K K K K K K K K K K K K K K
  P O N M L K J I H G F E D C B A Z Y X W V U T S R Q

L L L L L L L L L L L L L L L L L L L L L L L L L L L
  O N M L K J I H G F E D C B A Z Y X W V U T S R Q P

M M M M M M M M M M M M M M M M M M M M M M M M M M M 
  N M L K J I H G F E D C B A Z Y X W V U T S R Q P O

N N N N N N N N N N N N N N N N N N N N N N N N N N N
  M L K J I H G F E D C B A Z Y X W V U T S R Q P O N

O O O O O O O O O O O O O O O O O O O O O O O O O O O
  L K J I H G F E D C B A Z Y X W V U T S R Q P O N M

P P P P P P P P P P P P P P P P P P P P P P P P P P P
  K J I H G F E D C B A Z Y X W V U T S R Q P O N M L

Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q
  J I H G F E D C B A Z Y X W V U T S R Q P O N M L K

R R R R R R R R R R R R R R R R R R R R R R R R R R R
  I H G F E D C B A Z Y X W V U T S R Q P O N M L K J

S S S S S S S S S S S S S S S S S S S S S S S S S S S
  H G F E D C B A Z Y X W V U T S R Q P O N M L K J I

T T T T T T T T T T T T T T T T T T T T T T T T T T T
  G F E D C B A Z Y X W V U T S R Q P O N M L K J I H

U U U U U U U U U U U U U U U U U U U U U U U U U U U
  F E D C B A Z Y X W V U T S R Q P O N M L K J I H G

V V V V V V V V V V V V V V V V V V V V V V V V V V V
  E D C B A Z Y X W V U T S R Q P O N M L K J I H G F

W W W W W W W W W W W W W W W W W W W W W W W W W W W
  D C B A Z Y X W V U T S R Q P O N M L K J I H G F E

X X X X X X X X X X X X X X X X X X X X X X X X X X X
  C B A Z Y X W V U T S R Q P O N M L K J I H G F E D

Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
  B A Z Y X W V U T S R Q P O N M L K J I H G F E D C

Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z
  A Z Y X W V U T S R Q P O N M L K J I H G F E D C B

Manually generating a key with dice

The previous method described here had the flaw of bias towards some numbers while others do not appear as frequent resulting in a poor quality key - thankfully pointed out by SA4AMX.

One possible solution was found here, but it needs to be described dumb-simple for numbers 0 to 25. Here Thomas Langkaas explains some more.

tldr; Use 2 cube dices (6-sided). First roll selects range of second throw.
1  1  1  1  1  1  2  2  2  2  2  2  3  3  3  3  3  3  4  4  4  4  4  4  5  5
1  2  3  4  5  6  1  2  3  4  5  6  1  2  3  4  5  6  1  2  3  4  5  6  1  2
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

If first roll of the dice is a 3 and second roll is a 4, the random number is
15. If the first throw is a 6 you simply discard it and roll the dice again.
Same thing if the first roll is a 5 and the second roll is 3, 4, 5 or 6 - you
re-roll until you get either 1 or 2. If the first roll is 5 and the second
is 1 the number is 24. If the first roll is 5 and the second is 2 the number is
25.

Randomness

Krypto431 will use crypto/rand in the Golang implementation which in turn prefer getrandom(2) on Linux systems or /dev/urandom as a fallback. We can safely use /dev/urandom as it uses the exact same CSPRNG (Cryptographically Secure Pseudo Random Number Generator) as /dev/random. Entropy is not an issue if you have enough to start with (256 bits) and this program will not run at boot at the exact same time as a virtual machine that uses the exact same seed on every boot. So the issue with using /dev/urandom does not exist. By using crypto/rand on a Linux system above 3.17 (or something) we use getrandom(2) which will block until enough initial entropy has been gathered and will never block again - exactly what we want.

Reference: https://www.2uo.de/myths-about-urandom/

Secure key generation

Links to things related to cryptographically secure PRNG/RNG and

The Go math/rand PRNG is said to be from Plan9's rand(2) implementation and is an Additive Lagged Fibonacci Generator (ALFG) which is not considered secure by todays standards. https://en.wikipedia.org/wiki/Lagged_Fibonacci_generator.

It's an Additive Lagged Fibonacci Generator (ALFG)
described by S_n ≡ S_(n-273) + S_(n-607) mod 2^31.
It's the same code used in Plan 9's rand(2).

Source

Using Stefan Nilsson's approach by initiating a new source (not NewSource, but New(source)) makes it possible to use math/rand with an external pseudo random number generator (for example crypto/rand which is cryptographically secure).

// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
type Source interface {
	Int63() int64
	Seed(seed int64)
}

// A Source64 is a Source that can also generate
// uniformly-distributed pseudo-random uint64 values in
// the range [0, 1<<64) directly.
// If a Rand r's underlying Source s implements Source64,
// then r.Uint64 returns the result of one call to s.Uint64
// instead of making two calls to s.Int63.
type Source64 interface {
	Source
	Uint64() uint64
}

/* ... */

// New returns a new Rand that uses random values from src
// to generate other random values.
func New(src Source) *Rand {
	s64, _ := src.(Source64)
	return &Rand{src: src, s64: s64}
}

So we need at least Int63() and Seed(). Usually we can get a Uint64 and mask off a bit to get the Int63(), which is what Stefan Nilsson's implementation does.

The Seed() function can be completely empty in this case as it's already seeded by the operating system through crypto/rand.

https://youtu.be/cpqwp2H0SNo

431

X=0 ; for i in $(echo -n SA6MWA | hexdump -e '1/1 "%d "'); do let X=$X+$i ; done ; echo $X
431

# or...

echo -n SA6MWA | sum -s
431 1

Authors

See the AUTHORS file.

Documentation

Index

Constants

View Source
const (
	MinimumCallSignLength             int     = 2
	DefaultGroupSize                  int     = 5
	DefaultKeyLength                  int     = 350 // 70 groups, 5 groups per row is 14 rows total
	DefaultColumns                    int     = 110
	DefaultKeyColumns                 int     = 30
	DefaultPersistence                string  = "~/.krypto431.gob"
	DefaultKeyCapacity                int     = 50000                                   // 50k keys
	DefaultChunkCapacity              int     = 20                                      // 20 chunks
	DefaultEncodedTextCapacity        int     = DefaultKeyLength * 2                    // 700
	DefaultMessageCapacity            int     = 10000                                   // 10k messages
	DefaultPlainTextCapacity          int     = DefaultKeyLength * DefaultChunkCapacity // 7000
	DefaultPBKDF2Iteration            int     = 310000                                  // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
	DefaultMinimumPasswordEntropyBits float64 = 60
	MinimumSupportedKeyLength         int     = 20
	MinimumColumnWidth                int     = 85 // Trigraph table is 80 characters wide
	MinimumSaltLength                 int     = 32
	// Fixed salt for pbkdf2 derived keys. Can be changed using
	// krypto431.New(krypto431.WithSalt(hexEncodedSaltString)) when initiating a
	// new instance. You can use GenerateSalt() to generate a new salt for use in
	// WithSalt() and your UI program.
	DefaultSalt string = "d14461856f830fc5a1f9ba1b845fae5f61c54767ded39cf943174e6869b44476"
)

defaults, most are exported

View Source
const (

	// How many control runes/characters are required to change key at most?
	// (change table, then changeKeyChar = 2)
	ControlCharactersNeededToChangeKey int = 2
)
View Source
const LineBreak string = "\n"

Variables

View Source
var (
	CharacterTablePrimary   []rune = []rune(`ABCDEFGHIJKLMNOP RSTUVWXY¤`)
	CharacterTableSecondary []rune = []rune(`0123456789?-ÅÄÖ.Q,Z:+/¤¤¤¤`)

	// Encoding uses two character tables (primary and secondary). NB! Any changes
	// to the control characters in these tables need to be reflected in functions
	// encodeCharacter() and decodeCharacter().
	// CharacterTablePrimary = `ABCDEFGHIJKLMNOP RSTUVWXY¤`,
	// CharacterTableSecondary = `0123456789?-ÅÄÖ.Q,Z:+/¤¤¤¤`
	//
	// CODING LEGEND                    ⎘ Switch table (Z)
	// IDX ABCDEFGHIJKLMNOPQRSTUVWXYZ   ⬔ Toggle binary mode (W)
	// CT1 ABCDEFGHIJKLMNOP RSTUVWXY⎘   ↕ Toggle case (X)
	// CT2 0123456789?-ÅÄÖ.Q,Z:+/⬔↕⌥⎘   ⌥ Change key (Y)
	CharacterTables [][]rune = [][]rune{
		CharacterTablePrimary, CharacterTableSecondary,
	}
)
View Source
var (
	ErrNilPointer         = errors.New("received a nil pointer")
	ErrCipherTextTooShort = errors.New("message cipher text is too short to decipher")
	ErrNoKey              = errors.New("message has an invalid or no key")
	ErrKeyNotFound        = errors.New("key not found")
	ErrInvalidCoding      = errors.New("invalid character in encoded text (must be between A-Z)")
	ErrInvalidControlChar = errors.New("invalid control character")
	ErrTableTooShort      = errors.New("out-of-bounds, character table is too short")
	ErrUnsupportedTable   = errors.New("character table not supported")
	ErrOutOfKeys          = errors.New("can not encipher multi-key message, unable to find additional key(s)")
	ErrNoCallSign         = errors.New("need to specify your call-sign")
	ErrInvalidCallSign    = fmt.Errorf("invalid call-sign, should be at least %d characters long", MinimumCallSignLength)
	ErrNoPersistence      = errors.New("missing file name for persisting keys, messages and settings")
	ErrInvalidGroupSize   = errors.New("group size must be 1 or longer")
	ErrKeyTooShort        = fmt.Errorf("key length must be %d characters or longer", MinimumSupportedKeyLength)
	ErrTooNarrow          = fmt.Errorf("column width must be at least %d characters wide", MinimumColumnWidth)
	ErrKeyColumnsTooShort = errors.New("key column width less than group size")
	ErrFormatting         = errors.New("formatting error")
	ErrNotCipherText      = errors.New("plaintext not identified as ciphertext")
)
View Source
var (
	ErrNoRadiogramProvided = errors.New("no radiogram provided")
	ErrParsingRadiogram    = errors.New("unable to parse radiogram")
)
View Source
var (
	NilRunes          []rune = []rune("NIL")
	HelpTextRadiogram string = `` /* 610-byte string literal not displayed */

	// Full message format (012345 = Date-Time Group, abbreviated or full e.g
	// 012345AJAN23). Action addressees (adressmening), precedence or message
	// instructions (tjänsteanmärkning) are all non-capturing groups.
	//  AA BB CC DE DD 012345 == XY ZZ/P == COL 3 = ABCDE FGHIJ KLMNO = K
	//  DE VP 012345 == BA == COL 3 = ABCDE FGHIJ KLMNO = K
	//  Group 1 = TO (can be empty),
	//  Group 2 = FROM,
	//  Group 3 = DTG (optional, can be empty!),
	//  Group 4 = Message text (will include trailing = K, K, +, etc)
	MessageRegexpFull *regexp.Regexp = regexp.MustCompile(`(?i)([A-Z0-9/,\s]*)(?:\s+|^)DE\s+([A-Z0-9/]+)\s*([0-9]{6}[A-Z]{0,1}(?:JAN|FEB|MAR|APR|MAY|MAJ|JUN|JUL|AUG|SEP|OCT|OKT|NOV|DEC){0,1}(?:[0-9]{2}){0,1}){0,1}\s*==\s*(?:[A-Z0-9/,\s]*)\s*==\s*(?:[A-Z\s]*\s*[\d]*)\s*=\s*(.*)`)

	// Semi-full message format, without message instructions (tjänsteanmärkning),
	// optional group count (both are non-capturing groups).
	//  AA DE BB 012345 == VJ QJ 3 = ABCDE FGHIJ KLMNO = K
	//  AA DE BB 012345 == VJ QJ = HELLO WORLD = K
	//  Group 1 = TO (can be empty),
	//  Group 2 = FROM,
	//  Group 3 = DTG (optional, can be empty!),
	//  Group 4 = Message text (will include trailing = K, K, +, etc)
	MessageRegexpSemi *regexp.Regexp = regexp.MustCompile(`(?i)([A-Z0-9/,\s]*)(?:\s+|^)DE\s+([A-Z0-9/]+)\s*([0-9]{6}[A-Z]{0,1}(?:JAN|FEB|MAR|APR|MAY|MAJ|JUN|JUL|AUG|SEP|OCT|OKT|NOV|DEC){0,1}(?:[0-9]{2}){0,1}){0,1}\s*==\s*(?:[A-Z0-9/,\s]*\s*[\d]*)\s*=\s*(.*)`)

	// Short message format.
	//  AA BB CC DE VJ 012345 COL = HELLO WORLD = SECTION 2 GOES HERE, INCLUDED IN TXT = K
	//  AA BB CC DE VJ 012345 = HELLO WORLD
	//  AA DE VJ = HELLO WORLD
	//  DE VJ = HELLO WORLD = K
	//  Group 1 = TO (can be empty),
	//  Group 2 = FROM,
	//  Group 3 = DTG (optional, can be empty!),
	//  Group 4 = Message text (will include trailing = K, K, +, etc)
	MessageRegexpShort *regexp.Regexp = regexp.MustCompile(`(?i)([A-Z0-9/,\s]*)(?:\s+|^)DE\s+([A-Z0-9/]+)\s*([0-9]{6}[A-Z]{0,1}(?:JAN|FEB|MAR|APR|MAY|MAJ|JUN|JUL|AUG|SEP|OCT|OKT|NOV|DEC){0,1}(?:[0-9]{2}){0,1}){0,1}\s*(?:[A-Z]{0,4}\s*[\d]{0,4})\s*=\s*(.*)`)

	// Even shorter format.
	//  DE SA6MWA HELLO WORLD
	//  AB DE ZY 012345 WELL, HELLO THERE = K
	//  Group 1 = TO (can be empty),
	//  Group 2 = FROM,
	//  Group 3 = DTG (optional, can be empty!),
	//  Group 4 = Message text (will include trailing = K, K, +, etc)
	MessageRegexpMini *regexp.Regexp = regexp.MustCompile(`(?i)([A-Z0-9/,\s]*)(?:\s+|^)DE\s+([A-Z0-9/]+)\s*([0-9]{6}[A-Z]{0,1}(?:JAN|FEB|MAR|APR|MAY|MAJ|JUN|JUL|AUG|SEP|OCT|OKT|NOV|DEC){0,1}(?:[0-9]{2}){0,1}){0,1}\s*=*\s*(.*)`)

	// Regexp to match trailing = K, K, +, [AR], AR, etc in a string (e.g to clean
	// up the message text).
	//  MessageTrailRegexp.ReplaceAllString(messageText, "")
	MessageTrailRegexp *regexp.Regexp = regexp.MustCompile(`(?i)(\s*=\s*[K+]|\s[K+]|\s*\[AR\]|\s*=\s*AR)\s*$`)
)
View Source
var (
	RepeatPromptSuffix  string = "(repeat) "
	EncryptionPrompt    string = "Enter encryption key: "
	DecryptionPrompt    string = "Enter decryption key: "
	NewEncryptionPrompt string = "Enter new encryption key: "
)
View Source
var (
	ErrNoSalt       = errors.New(errUnableToDerivePFK + "instance is missing salt")
	ErrTooShortSalt = errors.New(errUnableToDerivePFK + "salt is too short")
	//ErrPasswordTooShort = fmt.Errorf(errUnableToDerivePFK+"too short, must be at least %d characters long", MinimumPasswordLength)
	ErrNilPFK         = errors.New("instance is missing key needed to encrypt or decrypt persistence")
	ErrInvalidPFK     = errors.New("persistence file key is invalid, must be 32 bytes long")
	ErrPasswordInput  = errors.New("password input error")
	ErrCopyKeyFailure = errors.New("copy key failure")
)
View Source
var (
	CustomMultilineQuestionTemplate string = `` /* 457-byte string literal not displayed */

)
View Source
var (
	KeyFooter string = `` +
		`CODING LEGEND                                              > Switch table (Z)` + LineBreak +
		`IDX A B C D E F G H I J K L M N O P Q R S T U V W X Y Z    ¤ Toggle binary mode (W)` + LineBreak +
		`CT1 A B C D E F G H I J K L M N O P   R S T U V W X Y >    ↕ Toggle case (X)` + LineBreak +
		`CT2 0 1 2 3 4 5 6 7 8 9 ? - Å Ä Ö . Q , Z : + / ¤ ↕ → >    → Change key (Y)` + LineBreak
)
View Source
var (
	MinimumPasswordEntropyBits float64 = DefaultMinimumPasswordEntropyBits
)
View Source
var Version string
View Source
var (
	Words = map[string]string{
		"No":  "No",
		"Yes": "Yes",
	}
)

Functions

func AllNeedlesInHaystack

func AllNeedlesInHaystack(needles *[][]rune, haystack *[][]rune, caseInsensitive ...bool) bool

AllNeedlesInHaystack returns true is all needles can be found in the haystack. Final variadic is optional, first true will match case-insensitive instead of matching case. Intended to find Keepers of Keys where needles are Message.Recipients and haystack is Key.Keepers.

func AnyNeedleInHaystack

func AnyNeedleInHaystack(needles *[][]rune, haystack *[][]rune) bool

func AnyOfThem

func AnyOfThem(haystack *[][]rune, needle *[]rune) bool

func AskAndConfirmPassword

func AskAndConfirmPassword(prompt string, minimumEntropyBits float64) (*[]byte, error)

func AskForPassword

func AskForPassword(prompt string, minimumLength int) *[]byte

AskForPassword prompts the user for a password/passphrase and returns a byte slice pointer or nil on error. The byte slice should be wiped with WipeBytes() as soon as a key has been derived from it.

func ByteCopy

func ByteCopy(src *[]byte) []byte

Returns a copy of a byte slice.

func BytePtr

func BytePtr(s []byte) *[]byte

func ColumnSizes

func ColumnSizes(rs [][][]rune, headerFields ...string) (columnSizes []int)

ColumnSizes calculates max length of each []rune in a slice of []rune slices. If optional column headers are provided (as a string slice), their length will be taken into account as well. Left for legacy, replaced by predictColumnSizes().

func EqualRunes

func EqualRunes(a *[]rune, b *[]rune) bool

Compare two rune slices. Returns true if they are equal, false if not.

func EqualRunesFold

func EqualRunesFold(a *[]rune, b *[]rune) bool

Same as EqualRunes, except EqualRunesFold is case-insensitive. Returns true if they are equal fold, false if not.

func GeneratePFK

func GeneratePFK() string

Generate a random 32 byte persistence file key for use when loading/saving the persistence file. Returns a hex encoded string of 64 characters that you can use as e.g environment variable and is compatible with SetPFKFromString(). Beware that strings are immutable in Go - the internal wipe functions can not be used to clear this sensitive data. The default password-based method in Load() and Save() use byte or rune slices which are (or can be) wiped in an attempt not to leave sensitive data around in memory after the program exits. Function will panic if there is an error.

func GenerateSalt

func GenerateSalt() string

Generate a hex string compatible with WithSaltString() and SetSaltFromString(). The hex string is MinimumSaltLength*2 (default 64) characters long which can be decoded into a MinimumSaltLength byte long byte slice using hex.DecodeString(). The salt is not a secret and should be shared with whoever is supposed to decrypt a Krypto431 persistence file (when, for example, exporting keys, messages or even main persistence files). Function will panic if there is an error.

func HighestInt

func HighestInt(number ...int) (highest int)

Maximum int in int slice. Alternative MaxInt function.

func IsTerminal

func IsTerminal() bool

IsTerminal returns true if os.Stdin is a terminal, false if not.

func JoinRunesToString

func JoinRunesToString(runes *[][]rune, separator string) string

Wrapper to strings.Join for a pointer to slices of rune slices. Returns a string with 'separator' as delimiter between items.

func LowestInt

func LowestInt(number ...int) (lowest int)

Minimum int in int slice. Alternative MinInt function.

func OldAskAndConfirmPassword

func OldAskAndConfirmPassword(prompt string, minimumLength int) (*[]byte, error)

Asks for password confirmation. Returns error if passwords don't match or a byte slice pointer if they do.

func RandomAlnumRunes

func RandomAlnumRunes(n int) []rune

RandomAlnumRunes generates a case-sensitive alpha-numeric string as a rune slice of the length n. Returns a rune slice.

func RandomWipe

func RandomWipe(b *[]rune) error

RandomWipe wipes a rune slice with random runes.

func RandomWipeBytes

func RandomWipeBytes(b *[]byte) error

RandomWipeBytes wipes a byte slice with random bytes.

func RuneCopy

func RuneCopy(src *[]rune) []rune

Returns a copy of a rune slice.

func RunePtr

func RunePtr(s []rune) *[]rune

func RunesToStrings

func RunesToStrings(runes *[][]rune) (stringSlice []string)

Generic function to convert an array of rune slices (runes) into a string slice.

func SetMinimumPasswordEntropyBits

func SetMinimumPasswordEntropyBits(entropy float64)

Configure go-password-validator minimum entropy for the entire krypto431 package. Entropy limit is not instance-scoped (yet).

func TrimRightRuneFunc

func TrimRightRuneFunc(s []rune, f func(rune) bool) []rune

func VettedCallSigns

func VettedCallSigns(callsigns ...string) [][]rune

VettedCallSigns is an alias for VettedKeepers.

func VettedKeepers

func VettedKeepers(keepers ...string) (vettedKeepers [][]rune)

Generic function to vet one or more keeper strings, comma/space-separated or not. Returns a slice of rune slices with the keepers for use in e.g Key.Keepers.

func VettedKeys

func VettedKeys(keys ...string) [][]rune

VettedKeys is an alias for VettedKeepers.

func VettedMessageIds

func VettedMessageIds(ids ...string) [][]rune

VettedMessageIds is NOT an alias for VettedKeepers :). Returns a slice of rune slices where case is preserved from the input (message IDs are case-sensitive). As with keepers, IDs can be space or comma separated.

func VettedRecipients

func VettedRecipients(recipients ...string) [][]rune

VettedRecipients is an alias for VettedKeepers.

func Wipe

func Wipe(b *[]rune) error

Wipe wipes a rune slice.

func WipeBytes

func WipeBytes(b *[]byte) error

WipeBytes wipes a byte slice.

func ZeroWipe

func ZeroWipe(b *[]rune) error

ZeroWipe wipes a rune slice with zeroes.

func ZeroWipeBytes

func ZeroWipeBytes(b *[]byte) error

ZeroWipeBytes wipes a byte slice with zeroes.

Types

type GroupFormatter

type GroupFormatter interface {
	Groups() (*[]rune, error)
	GroupsBlock() (*[]rune, error)
}

Returns a grouped string according to GroupSize set in the instance (Krypto431). Keys and Messages implement this interface.

type Key

type Key struct {
	Id          []rune
	Runes       []rune
	Keepers     [][]rune
	Created     dtg.DTG
	Expires     dtg.DTG
	Used        bool
	Compromised bool
	Comment     []rune
	// contains filtered or unexported fields
}

Key struct holds a key. Keepers is a list of call-signs or other identifiers that have access to this key (and can use it for encryption/decryption). The proper procedure is to share the key with it's respective keeper(s).

func (*Key) AddKeeper

func (k *Key) AddKeeper(keepers ...[]rune) *Key

AddKeeper adds keeper(s) to the Keepers slice if not already there. Can be chained.

func (*Key) CommentString

func (k *Key) CommentString() string

func (*Key) CompromisedString

func (k *Key) CompromisedString() string

Returns Yes if key is marked compromised, No if not.

func (*Key) ContainsKeeper

func (k *Key) ContainsKeeper(keepers ...[]rune) bool

func (*Key) GetCallSign

func (k *Key) GetCallSign() []rune

func (*Key) GetInstance

func (k *Key) GetInstance() *Krypto431

Return the Krypto431 instance (non-exported field) of a key.

func (Key) GoString

func (k Key) GoString() string

func (*Key) Groups

func (k *Key) Groups() (*[]rune, error)

Groups for keys return a rune slice where each number of GroupSize runes are separated by a space. Don't forget to Wipe() this slice when you are done!

func (*Key) GroupsBlock

func (k *Key) GroupsBlock() (*[]rune, error)

GroupsBlock returns a string-as-rune-slice representation of the key where each group is separated by a space or new line if a line becomes longer than Krypto431.KeyColumns (e.g DefaultKeyColumns). Don't forget to Wipe(b []rune) this slice when you are done!

func (*Key) IdString

func (k *Key) IdString() string

func (*Key) IsExpired

func (k *Key) IsExpired() bool

Check if key is still valid or has expired, returns true if still valid, false if not.

func (*Key) IsValid

func (k *Key) IsValid(d time.Duration) bool

Check if key is valid d amount of time before expiring (e.g 24*time.Hour for one day). Returns true if key does not expire within d time or false if it does.

func (*Key) IsValidOneDay

func (k *Key) IsValidOneDay() bool

Check if key is valid at least one day before expiring. Returns true if key does not expire within that time, false if not.

func (*Key) IsValidOneMonth

func (k *Key) IsValidOneMonth() bool

Check if key is valid at least 30 days before expiring. Returns true if key does not expire within that time, false if not.

func (*Key) IsValidOneYear

func (k *Key) IsValidOneYear() bool

Check if key is valid at least one year (365 days) before expiring. Returns true if key does not expire within that time, false if not.

func (*Key) JoinKeepers

func (k *Key) JoinKeepers(separator string) string

func (*Key) KeyLength

func (k *Key) KeyLength() int

KeyLength() returns the length of this key instance.

func (*Key) RandomWipe

func (k *Key) RandomWipe() error

RandomWipe overwrites key with random runes.

func (*Key) RemoveKeeper

func (k *Key) RemoveKeeper(keepers ...[]rune) *Key

RemoveKeeper removes keeper(s) from the Keepers slice if found. Can be chained.

func (*Key) SetInstance

func (k *Key) SetInstance(instance *Krypto431) *Key

Set instance of Krypto431 (non-exported field) for a key. Can be chained.

func (*Key) String

func (k *Key) String() string

Key_String returns a fully printable key with the DIANA reciprocal table and coding legend. Instructions are not included in the output.

func (*Key) UsedOrNotString

func (k *Key) UsedOrNotString(used string, notUsed string) string

Returns string used if key is marked used or string notUsed if not marked used.

func (*Key) UsedString

func (k *Key) UsedString(rightSpacing ...int) string

Returns Yes if key is marked used, No if not. Optional rightSpacing pads the output string with trailing spaces.

func (*Key) Wipe

func (k *Key) Wipe() error

Wipe overwrites key with either random runes or zeroes.

func (*Key) ZeroWipe

func (k *Key) ZeroWipe() error

ZeroWipe zeroes a key.

type Krypto431

type Krypto431 struct {
	GroupSize  int
	KeyLength  int
	Columns    int
	KeyColumns int
	Keys       []Key
	Messages   []Message
	CallSign   []rune
	// contains filtered or unexported fields
}

Krypto431 store generated keys, plaintext, ciphertext, callsign(s) and configuration items. CallSign is mandatory (something identifying yourself in message handling). It will be converted to upper case. Mutex and persistance file (persistence) are not exported meaning values will not be persisted to disk.

func New

func New(opts ...Option) Krypto431

New creates a new Krypto431 instance.

func (*Krypto431) Assert

func (k *Krypto431) Assert() error

Asserts that settings in the instance are valid. Function is intended to be executed after New() to assert that settings are valid.

func (*Krypto431) CallSignString

func (k *Krypto431) CallSignString() string

func (*Krypto431) Close

func (k *Krypto431) Close()

Close is an alias for Krypto431.Wipe()

func (*Krypto431) ContainsKeyId

func (k *Krypto431) ContainsKeyId(keyId *[]rune) bool

ContainsKeyId checks if the Krypto431.Keys slice already contains Id and return true if it does, false if it does not.

func (*Krypto431) ContainsMessageId

func (k *Krypto431) ContainsMessageId(msgId *[]rune) bool

ContainsMessageId checks if the Krypto431.Messages slice already contains Id and return true if it does, false if it does not.

func (*Krypto431) DeleteKey

func (k *Krypto431) DeleteKey(keyIds ...[]rune) (int, error)

DeleteKey removes one or more keys from the instance's Keys slice wiping the key before deleting it. Returns number of keys deleted or error on failure.

func (*Krypto431) DeleteKeyByString

func (k *Krypto431) DeleteKeyByString(keyIds ...string) (int, error)

DeleteKeyByString is an alias for DeleteKey where key IDs are issued as strings instead of rune slices.

func (*Krypto431) DeleteKeysBySummaryString

func (k *Krypto431) DeleteKeysBySummaryString(summaryStrings ...string) (int, error)

func (*Krypto431) DeleteMessage

func (k *Krypto431) DeleteMessage(messageIds ...[]rune) (int, error)

DeleteMessage removes one or more messages from the instance's Messages slice wiping the message before deleting it. Returns number of messages deleted or error on failure.

func (*Krypto431) DeleteMessageByString

func (k *Krypto431) DeleteMessageByString(messageIds ...string) (int, error)

DeleteMessageByString is an alias for DeleteMessage where message IDs are issued as strings instead of rune slices.

func (*Krypto431) DeleteMessagesBySummaryString

func (k *Krypto431) DeleteMessagesBySummaryString(summaryStrings ...string) (int, error)

func (*Krypto431) DerivePFKFromPassword

func (k *Krypto431) DerivePFKFromPassword(password *[]byte) error

DerivePFKFromPassword uses PBKDF2 to produce the 32 byte long key used to encrypt/decrypt the persistence file. The salt in the Krypto431 instance is used to derive the key, either the default fixed salt or one that you provided earlier (e.g krypto431.New(krypto431.WithSalt(my64charHexString))).

func (*Krypto431) DerivePFKFromPasswordWithValidation

func (k *Krypto431) DerivePFKFromPasswordWithValidation(password *[]byte) error

Same as DerivePFKFromPassword, but validates the password against go-password-validator according to set minimum entropy bits.

func (*Krypto431) ExportKeys

func (k *Krypto431) ExportKeys(filterFunction func(key *Key) bool, opts ...Option) Krypto431

Krypto431.ExportKeys() returns a new instance based on the current instance where only the keys that pass the filterFunction are copied over (entirely w/o messages). The filterFunction must return true for each key to to export (copy to the new instance) and false to not copy the key. The new instance's persistence field (filename of the save-file) will be empty unless option function WithPersistence(filename) is specified (can also be configured afterwards with SetPersistence()). Any Option function (With*) can be used to override any copied field.

func (*Krypto431) FindKey

func (r *Krypto431) FindKey(recipients ...[]rune) *Key

FindKey returns the first un-used key of the configured group size where all recipients are keepers of that key. If the recipient slice is empty, it will find the first un-used anonymous key (a key without any keepers). Function returns a pointer to the key. FindKey will not mark the key as used.

func (*Krypto431) GenerateKeys

func (k *Krypto431) GenerateKeys(n int, expire *string, keepers ...string) error

GenerateKeys creates n amount of keys. The expire argument is a Date-Time Group when the key(s) is/are to expire (DDHHMMZmmmYY). If expire is nil, keys will expire one year from current time. If no keepers are provided, keys will be considered anonymous.

func (*Krypto431) GetCallSign

func (k *Krypto431) GetCallSign() []rune

func (*Krypto431) GetKey

func (r *Krypto431) GetKey(keyId []rune) (*Key, error)

GetKey() searches for a Key object with an Id of keyId and returns a pointer to this Key or error if not found.

func (*Krypto431) GetPFK

func (k *Krypto431) GetPFK() *[]byte

GetPFK returns a byte slice pointer to the instance persistence file key (PFK). Function exists as the persistenceKey field is not exported.

func (*Krypto431) GetPFKString

func (k *Krypto431) GetPFKString() string

GetPFKString (yes, it's Pascal-case) returns a hex-encoded string representation of the persistence file key (or empty if there is no PFK in this instance). Function exists as the persistenceKey field is not exported.

func (*Krypto431) GetPersistence

func (k *Krypto431) GetPersistence() string

GetPersistence returns the non-exported persistence string (save-file) from an instance.

func (*Krypto431) GetSalt

func (k *Krypto431) GetSalt() *[]byte

Similar to GetPFK, but return pointer to the salt.

func (*Krypto431) GetSaltString

func (k *Krypto431) GetSaltString() string

Similar to GetPFKString, but return a hex-encoded string of the salt.

func (*Krypto431) ImportKeys

func (k *Krypto431) ImportKeys(filterFunction func(key *Key) bool, opts ...Option) (int, error)

Krypto431_ImportKeys() does the opposite of ExportKeys(). The filterFunction runs on each key from the persistence file specified in the opts variadic WithPersistence(filename), for example:

ImportKeys(myFilter, krypto431.WithPersistence(filename), krypto431.WithInteractive(true))

The default salt is used when loading the file and the PFK is empty. Load() will interactively ask for password only if WithInteractive(true) is provided as an option or it will return an error. To override PFK and/or salt, use WithPFK() or WithPFKString(), WithSalt() or WithSaltString(). To use the key and salt from the current instance for the imported instance, you can do the following:

k.ImportKeys(f, WithPersistence(file), WithPFK(k.GetPFK()), WithSalt(k.GetSalt()))

If an imported key ID already exists in the receiving instance and interactive mode is enabled, function will ask for confirmation before overwriting. To force overwriting without asking, add WithOverwriteExistingKeysOnImport(true).

func (*Krypto431) IsInteractive

func (k *Krypto431) IsInteractive() bool

IsInteractive returns true if instance functions can work in interactive mode.

func (*Krypto431) IsOverwriteExistingKeysOnImport

func (k *Krypto431) IsOverwriteExistingKeysOnImport() bool

func (*Krypto431) KeysAsText

func (k *Krypto431) KeysAsText(filter func(key *Key) bool) string

KeysAsText returns a formatted multi-line string with all keys according to filter function. For example, to return all keys not marked `used` as one big printable string...

s := krypto431.KeysAsText(func(k *krypto431.Key) bool { return !k.Used })

func (*Krypto431) KeysPDF

func (k *Krypto431) KeysPDF(filter func(key *Key) bool, filename string) error

func (*Krypto431) KeysTextFile

func (k *Krypto431) KeysTextFile(filter func(key *Key) bool, filename string) error

func (*Krypto431) Load

func (k *Krypto431) Load() error

Krypto431_Load() loads a Krypto431 instance from the configured persistence file (k.persistence). Only exported fields will be populated.

func (*Krypto431) MarkKeyUsed

func (r *Krypto431) MarkKeyUsed(keyId []rune, used bool) error

MarkKeyUsed() looks for the keyId among the instance's Keys and sets the Used property to true or false depending on what the "used" variable is set to.

func (*Krypto431) MessagesAsText

func (k *Krypto431) MessagesAsText(filter func(msg *Message) bool) string

MessagesAsText returns a formatted multi-line string with all messages according to filter function. For example, to return all messages as one big printable string...

s := krypto431.MessagesAsText(func(k *krypto431.Message) bool { return true })

func (*Krypto431) MessagesPDF

func (k *Krypto431) MessagesPDF(filter func(msg *Message) bool, filename string) error

func (*Krypto431) MessagesTextFile

func (k *Krypto431) MessagesTextFile(filter func(msg *Message) bool, filename string) error

func (*Krypto431) NewKey

func (k *Krypto431) NewKey(expire time.Time, keepers ...string) *Key

NewKey generates a new key. The current implementation generates a random group not yet in the Krypto431 construct. Keepers can be one call-sign per variadic, comma-separated call-signs or a combination of both.

func (*Krypto431) NewTextMessage

func (k *Krypto431) NewTextMessage(msg ...string) (*Message, error)

NewTextMessage creates a new message from a radiogram (Swedish Armed Forces telegraphy radiogram which can be thought of as simplified ACP 124). First argument is the radiogram, optional second argument is a key ID which - if specified - will override the key finder function if message is an outgoing message (a used or compromised key will not be allowed). If the From field (after DE in the radiogram) has the same call-sign as the instance's CallSign the message is considered an outgoing message. If From (DE) field is not your call-sign the message is considered an incoming message.

Outgoing messages (DE yourCallSign) will be enciphered with a key found automatically or with key specified as an optional second argument.

Incoming messages (DE notYourCallSign) will be attempted to be deciphered. If deciphering fails and it is still a valid incoming enciphered message, the cipher-text will stay in the PlainText field. The message function TryDecipherPlainText can safely be run prior to future presentation of the message, perhaps when the key - if initially missing in your store - has been obtained, deciphering may succeed (for example).

func (*Krypto431) NewTextMessageFromReader

func (k *Krypto431) NewTextMessageFromReader(r io.Reader) (*Message, error)

NewTextMessageFromReader is similar to PromptNewTextMessage except radiogram is read from an io.Reader (until EOF). There is no output except warnings/errors. BUG(sa6mwa): NewTextMessage and ParseRadiogram need to implement io.Reader instead.

func (*Krypto431) NewUniqueMessageId

func (k *Krypto431) NewUniqueMessageId() []rune

NewUniqueMessageId generates an alpha-numeric ID that is unique among the instance's messages. Returns a rune slice of length 4.

func (*Krypto431) ParseRadiogram

func (k *Krypto431) ParseRadiogram(radiogram string) (*Message, error)

ParseRadiogram attempts to break out the Recipients, From (DE), Date-Time Group and PlainText from a Swedish Armed Forces radiotelegraphy message formatted as transmitted/received. Function returns a pointer to a new Message object. If DTG is empty, Message time will be set to current local system time. ParseRadiogram will always put the radiogram text in the PlainText field and leave KeyId empty. TryDecipherPlainText can be safely called on the return message object (especially with dryrun==true) to automatically attempt to identify the PlainText as CipherText with prepended KeyId. By default, TryDecipherPlainText will attempt to decipher the PlainText which will result in a populated KeyId and CipherText field as well as the actual (deciphered) PlainText.

func (*Krypto431) PromptNewTextMessage

func (k *Krypto431) PromptNewTextMessage() (*Message, error)

PromptNewTextMessage prompts the user to enter a a new text message as a radiogram. If os.Stdin is not a terminal, radiogram is read from stdin without prompt. Returns a pointer to the new message or error on failure.

func (*Krypto431) Save

func (k *Krypto431) Save() error

Krypto431_Save persists a Krypto431 instance to file. The output file is a gzipped GOB (Go Binary) which is XSalsa20Poly1305 encrypted using a 32 byte key set via DerivePFKFromPassword(), SetPFKFromString() or WithPFK().

func (*Krypto431) SetCallSign

func (k *Krypto431) SetCallSign(callsign string) error

Validates and sets the instance's call-sign.

func (*Krypto431) SetInteractive

func (k *Krypto431) SetInteractive(state bool) *Krypto431

SetInteractive is provided to set the interactive non-exported field in an instance (true=on, false=off).

func (*Krypto431) SetOverwriteExistingKeysOnImport

func (k *Krypto431) SetOverwriteExistingKeysOnImport(state bool) *Krypto431

SetOverwriteExistingKeysOnImport answers

func (*Krypto431) SetPFKFromPassword

func (k *Krypto431) SetPFKFromPassword(password string) error

Similar to SetPFKFromString() except it derives the key from a passphrase via the PBKDF2 function DerivePFKFromPassword. The instance's configured salt is used and need to be set before calling this function.

func (*Krypto431) SetPFKFromString

func (k *Krypto431) SetPFKFromString(key string) error

Takes persistence file key (PFK) from a hex encoded string (from perhaps GeneratePFK()), converts it into a byte slice and sets it as the instance's PFK which will override the password-based key derivative function and the use of a salt. The key must be 32 bytes long (64 character long hex encoded string). GeneratePFK() is available to generate a new random persistence file key and return a hex encoded string. Beware that strings are immutable in Go which means the internal wipe functions can not be used to clear this sensitive string after closing or wiping the instance. The default password-based method use byte or rune slices which are (or can be) wiped in an attempt not to leave sensitive data around in memory after the program exits.

func (*Krypto431) SetPersistence

func (k *Krypto431) SetPersistence(filename string)

SetPersistence sets the non-exported persistence property in the instance to filename.

func (*Krypto431) SetSaltFromString

func (k *Krypto431) SetSaltFromString(salt string) error

Takes salt from a hex encoded string, converts it into a byte slice and sets it as the instance's salt for the password-based key derivative function used in Load() and Save(). Beware! If you loose the salt you used for encrypting your persistance-file it will be practically impossible to decrypt it even if you know the password.

func (*Krypto431) SummaryOfKeys

func (k *Krypto431) SummaryOfKeys(filter func(key *Key) bool) (header []rune, lines [][]rune)

continue here TODO add sort function to list (for list, delete, etc). idea: let digest be pointer slice with keys, sort pointers based on date, etc...

sort.Slice(dateSlice, func(i, j int) bool {
		return dateSlice[i].sortByThis.Before(dateSlice[j].sortByThis)
	})

func (*Krypto431) SummaryOfMessages

func (k *Krypto431) SummaryOfMessages(filter func(msg *Message) bool) (header []rune, lines [][]rune)

func (*Krypto431) Wipe

func (k *Krypto431) Wipe()

Wipe instance assigned method wipes all in-memory keys and texts (plaintext and ciphertext) with random runes or zeroes in an attempt to keep sensitive information for as short time as possible in memory. Whenever keys or ciphertexts are written to the database they are wiped automatically, respectively, but it is up to the user of the API to call Wipe() or Close() when using the methods to read keys and ciphertext from database, file or stdin when done processing them.

type Message

type Message struct {
	Id         []rune
	Recipients [][]rune
	From       []rune
	DTG        dtg.DTG
	KeyId      []rune
	PlainText  []rune
	Binary     []byte
	CipherText []rune
	Radiogram  []rune // Raw radiogram
	// contains filtered or unexported fields
}

Message holds plaintext and ciphertext. To encipher, you need to populate the PlainText (OR Binary) field, the rest will be updated by the Encipher function which will choose the next available key. If PlainText is longer than the key, the Encrypt function will use another key where the key's Keepers field matches all of the Recipients. If there are not enough keys to encrypt the message, Encrypt will fail. Encrypt will cache all non-used keys from the database matching the Recipients into the instance Keys slice before enciphering. To decrypt you need to have ciphertext in the CipherText field and the start KeyId. All binary data in the message will be appended to the Binary field. There is no method (yet) to figure out which of your keys can be used to decipher the message. If the KeyId is not already in your instace's Keys slice it will be fetched from the database or fail. The KeyId should be the first group in your received message.

func (*Message) AddRecipient

func (m *Message) AddRecipient(recipients ...[]rune) *Message

AddRecipient adds recipient(s) to the Recipients slice if not already there. Can be chained.

func (*Message) ContainsRecipient

func (m *Message) ContainsRecipient(recipients ...[]rune) bool

ContainsRecipient returns true if all recipients are recipients of this message.

func (*Message) Decipher

func (m *Message) Decipher() error

Decipher deciphers the CipherText field into the PlainText field of a Message object. PlainText will be replaced with deciphered text if text already exists. Decipher does not use a separate decoding function as simultaneous decoding is needed to support CipherText enciphered with multiple keys. If deciphering succeeds, all keys used in the message will be marked `used`.

func (*Message) Encipher

func (m *Message) Encipher() error

Encipher() enciphers the PlainText field into the CipherText field of a Message object. Verbs encrypt and decrypt are only used for AES encryption/decryption of the persistance file, while words encipher and decipher are used for message ciphering in Krypto431.

func (*Message) EnrichWithKey

func (m *Message) EnrichWithKey() error

EnrichWithKey finds the first appropriate key for this Message structure where each of the Messages Recipients are Keepers of the same key. If CipherText and KeyId already appear to be present, function will just return. If CipherText appear to be present, but message KeyId is empty it will return an error. If there is no CipherText or KeyId, the function will try to find one where all recipients are keepers of this key. The message KeyId will be used by diana.Trigraph during encryption/decryption.

func (*Message) GetInstance

func (m *Message) GetInstance() *Krypto431

Return the Krypto431 instance (non-exported field) of a message.

func (Message) GoString

func (m Message) GoString() string

func (*Message) Groups

func (m *Message) Groups() (*[]rune, error)

Groups for messages return a rune slice where each group (GroupSize) is separated by space. Don't forget to Wipe() this slice when you are done!

func (*Message) GroupsBlock

func (m *Message) GroupsBlock() (*[]rune, error)

GroupsBlock returns a string-as-rune-slice representation of the message cipher text where each group is separated by a space or new line if a line becomes longer than Krypto431.Columns (or DefaultColumns). Don't forget to Wipe() this slice when you are done!

func (*Message) IdString

func (m *Message) IdString() string

func (*Message) IsMyCall

func (m *Message) IsMyCall() bool

IsMyCall returns true if From field is the same as the instance's call-sign, false if not.

func (*Message) JoinRecipients

func (m *Message) JoinRecipients(separator string) string

func (*Message) QRZ

func (m *Message) QRZ() []rune

func (*Message) QRZString

func (m *Message) QRZString() string

func (*Message) RandomWipe

func (m *Message) RandomWipe()

RandomWipe assigned method for Text wipes PlainText, CipherText and KeyId fields.

func (*Message) RemoveRecipient

func (m *Message) RemoveRecipient(recipients ...[]rune) *Message

RemoveRecipient removes recipient(s) from the Recipients slice if found. Can be chained.

func (*Message) SetInstance

func (m *Message) SetInstance(instance *Krypto431)

Set instance of Krypto431 (non-exported field) for a message.

func (*Message) String

func (m *Message) String(width ...int) string

Message_String returns a formatted output intended to print. By default the width is 80 to fit consoles, but can be changed with the optional width variadic (first item in slice is used for column width).

func (*Message) TryDecipherPlainText

func (m *Message) TryDecipherPlainText(dryrun ...bool) error

TryDecipherPlainText attempts to identify PlainText that is actually CipherText prepended with a key. If optional dryrun is not true, function will copy possible key ID and ciphertext to the message and attempt to decipher it. On failure to decipher, the message object (KeyId and PlainText) is restored. Function will not run if the message has the KeyId filled in or the first group of the PlainText (key ID) can not be found in the key store. Returns error if CipherText detection/decipher was unsuccessful, nil if successful.

func (*Message) Wipe

func (m *Message) Wipe()

Wipe overwrites key, plaintext and ciphertext with random runes or zeroes. The order is highest priority first (plaintext), then ciphertext and finally the keyid. Nilling the rune slices should promote it for garbage collection.

func (*Message) ZeroWipe

func (m *Message) ZeroWipe()

ZeroWipe assigned method for PlainText writes zeroes to Text and EncodedText fields.

type Option

type Option func(k *Krypto431)

Option fn type for the New() construct.

func WithCallSign

func WithCallSign(cs string) Option

func WithColumns

func WithColumns(n int) Option

func WithGroupSize

func WithGroupSize(n int) Option

func WithInteractive

func WithInteractive(b bool) Option

func WithKeyColumns

func WithKeyColumns(n int) Option

func WithKeyLength

func WithKeyLength(n int) Option

func WithMutex

func WithMutex(mu *sync.Mutex) Option

func WithOverwriteExistingKeysOnImport

func WithOverwriteExistingKeysOnImport(b bool) Option

func WithOverwritePersistenceIfExists

func WithOverwritePersistenceIfExists(b bool) Option

func WithPFK

func WithPFK(key *[]byte) Option

WithPFK overrides deriving the encryption key for the persistance-file from a password by using the key directly. Must be 32 bytes long. Beware! Underlying byte slice will be wiped when closing or wiping the Krypto431 instance, but the New() function returns a reference not a pointer meaning there could still be a copy of this key in memory after wiping.

func WithPFKString

func WithPFKString(hexEncodedString string) Option

As WithPFK, but takes a string and attempts to hex decode it into a byte slice. Not recommended to use as it doesn't fail on error just nils the key and leaves memory traces that can not be wiped. Use SetPFKFromString() on the instance after New() instead.

func WithPersistence

func WithPersistence(savefile string) Option

func WithSalt

func WithSalt(salt *[]byte) Option

WithSalt() can be used to override the default fixed salt with a custom salt. Beware that the underlying byte slice will be wiped when closing or wiping the Krypto431 instance. Use hex.DecodeString() to generate a 32 byte slice from a 64 byte hex string produced by for example GenerateSalt().

func WithSaltString

func WithSaltString(salt string) Option

WithSaltString() runs the salt string through hex.DecodeString() to derive a byte slice that, if at least 32 bytes long, is used instead of the default internal fixed salt. Not recommended as it just nils the salt on error, use WithSalt() and solve decoding with e.g hex.DecodeString() prior to instance creation. You can also use SetSaltFromString() on the instance after New().

type Wiper

type Wiper interface {
	Wipe() error
	RandomWipe() error
	ZeroWipe() error
}

Wiper interface (for Keys and Messages only). Not used internally in package.

Notes

Bugs

  • NewTextMessage and ParseRadiogram need to implement io.Reader instead.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL