Liking cljdoc? Tell your friends :D

license clojars

Intro

This is a thin wrapper for Bouncycastle library to work with GOST algorithms - Russian cryptographic standards.

The library provides:

  • encryption, mac using GOST 28147-89, GOST 3412-2015;

  • digest, hmac using GOST3411-94/2012 256 and 512 bits;

  • signature using GOST3410-2012 256 and 512 bit key length;

  • key generation: secret keys, public/private keys, password based keys;

  • compression + encryption + mac / decryption + check mac + decompression

  • encrypting with EC public keys / decrypting with EC private keys

  • save/load keys (secret, private, public) to PEM format.

Usage

Add dependency to the deps.edn:

:deps { org.rssys/gost {:mvn/version "0.2.0"}}

Require necessary namespaces:

(require '[org.rssys.gost.encrypt :as e])
(require '[org.rssys.gost.digest :as d])
(require '[org.rssys.gost.common :as common])

Secret key generation

This is high-level functions.

;; To generate a secret key for the GOST3412-2015 use `generate-secret-key` function.
;; This will return a 256-bit random secret key as a SecretKeySpec object.
;; The algorithm is set to GOST3412-2015
(def secret-key-2015 (e/generate-secret-key))
(e/algo-name secret-key-2015) ;; => GOST3412-2015

;; To generate a secret key GOST28147-89 use `generate-secret-key` function with a parameter.
;; This will return a 256-bit random secret key as a SecretKeySpec object.
;; The algorithm is set to GOST28147.
(def secret-key-89 (e/generate-secret-key e/gost28147))
(e/algo-name secret-key-89) ;; => GOST28147

;; To convert a SecretKeySpec to a byte array:
(e/secret-key->byte-array secret-key-2015) ;; => [B
;; [-38, -86, 71, -42, -69, 73, -33, 53, 72, 80, 38, 26, 57, 69, -114, -1,
;; -119, 13, 113, -84, -31, 54, -128, 114, -79, -55, 85, 126, 105, -96,
;; -37, -128]

;; To convert a byte array to SecretKeySpec:
(e/byte-array->secret-key (byte-array [-38, -86, 71, -42, -69, 73, -33, 53, 72, 80, 38, 26, 57, 69, -114, -1,
                                       -119, 13, 113, -84, -31, 54, -128, 114, -79, -55, 85, 126, 105, -96,
                                       -37, -128])) ;; => #object[javax.crypto.spec.SecretKeySpec

;; We can generate a secret key bytes from a password.
;; This function always return the same bytes value from the same String password.
;; By default, it uses min 10000 iterations of PBKDF2WITHHMACGOST3411 algorithm, recommended by NIST
(e/generate-secret-bytes-from-password "qwerty12345") ;; => [B
;;[-113, 62, 87, -90, 116, -44, -20, -98, 4, -108, 77, -59, -22, 25, -73,
;; 20, -31, 62, -86, 19, 103, 81, -64, 32, 74, 81, -32, -97, -78, 123,
;; -82, -70]

;; To convert it to SecretKeySpec
(e/byte-array->secret-key
  (e/generate-secret-bytes-from-password "qwerty12345")) ;; => #object[javax.crypto.spec.SecretKeySpec

Encryption

This is high-level functions.

(def message "This text has length = 32 bytes.")


;; To encrypt a byte array (any binary content) in a most secured way just use `protect-bytes` function.
;; The encryption algorithm GOST3412-2015 or GOST28147-89 is already set in SecretKeySpec.
;; This function calculates Mac for plain data, then
;; compress a plain data to hide information structure, then
;; encrypts data and Mac in CFB mode with always random IV.
;; The encrypted bytes from the same message and same key are always different!
(def encrypted-message (e/protect-bytes secret-key-2015 (.getBytes message))) ;; Returns bytes array with structure:
;; [random(IV), encrypted(Mac), encrypted(compressed-data)]

;; To decrypt and restore a plain text just use `unprotect-bytes` function.
;; The decryption algorithm GOST3412-2015 or GOST28147-89 is already set in SecretKeySpec.
;; This function decrypts Mac and data, then
;; decompress data, then calculate Mac for decompressed data, then
;; compare Mac from a message and Mac calculated.
;; If Macs are the same then return plain data, otherwise throw an Exception.
(def decrypted-message (e/unprotect-bytes secret-key-2015 encrypted-message))

(= message (String. ^bytes decrypted-message)) ;; => true

;; To encrypt a file (any binary content) in a most secured way just use `protect-file` function.
;; The encryption algorithm GOST3412-2015 or GOST28147-89 is already set in SecretKeySpec.
;; This function calculates Mac for plain file, then
;; compress a plain file to hide information structure, then
;; encrypts data and Mac in CFB mode with always random IV.
;; The encrypted bytes from the same message and same key are always different!
(e/protect-file secret-key-2015 "dev/src/examples/plain32.txt" "target/plain32.enc") ;; Encrypted file has structure:
;; random(IV), encrypted(Mac), encrypted(compressed-data).

;; To decrypt a file just use `unprotect-file` function.
;; The decryption algorithm GOST3412-2015 or GOST28147-89 is already set in SecretKeySpec.
;; This function decrypts Mac and data, then
;; decompress data in a file, then calculate Mac for decompressed data, then
;; compare Mac from the message and Mac calculated.
;; If Macs are the same then return output file name as String, otherwise throw an Exception.
(e/unprotect-file secret-key-2015 "target/plain32.enc" "target/plain32.txt")

(= (slurp "dev/src/examples/plain32.txt") (slurp "target/plain32.txt")) ;; => true

Mac generation

This is high-level functions.

;; To calculate Mac for a file (any binary file) use `mac-stream` function.
;; The encryption algorithm GOST3412-2015 or GOST28147-89 is already set in SecretKeySpec.
;; Mac value from the same data and same SecretKeySpec is always the same.
(e/mac-stream secret-key-2015 "dev/src/examples/plain32.txt") ;; => [B
;; [-111, 125, 10, -34, -109, -109, 41, 115, 81, 61, -90, -80, 16, 71, -108, 91]

;; To calculate Mac for a byte array (any binary file) use the same `mac-stream` function.
;; The encryption algorithm GOST3412-2015 or GOST28147-89 is already set in SecretKeySpec.
;; Mac value from the same data and same SecretKeySpec is always the same.
(e/mac-stream secret-key-2015 (.getBytes message)) ;; => [B
;; [-111, 125, 10, -34, -109, -109, 41, 115, 81, 61, -90, -80, 16, 71, -108, 91]

Digest

This is high-level functions.

digest.clj
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; High-level functions

(require '[org.rssys.gost.digest :as d])
(require '[org.rssys.gost.common :as common])

(def message "The quick brown fox jumps over the lazy dog")

;; To generate GOST3411-94 digest from byte array use `digest-3411-94` function
(def d1 (d/digest-3411-94 (.getBytes message)))

(common/bytes-to-hex d1)                                    ;; =>
;; "9004294a361a508c586fe53d1f1b02746765e71b765472786e4770d565830a76"

;; To generate GOST3411-94 digest from file use the same `digest-3411-94` function
(def d2 (d/digest-3411-94 "dev/src/examples/plain32.txt"))

(common/bytes-to-hex d2)                                    ;; =>
;; "94ca6fc62ae26d3bb0109c16e6a5749c291bbdd0cdf5231e3f4073679227b9fb"

;; To generate GOST3411-2012-256 digest from byte array use `digest-2012-256` function
(def d3 (d/digest-2012-256 (.getBytes message)))

(common/bytes-to-hex d3)                                    ;; =>
;; "3e7dea7f2384b6c5a3d0e24aaa29c05e89ddd762145030ec22c71a6db8b2c1f4"

;; To generate GOST3411-2012-256 digest from file use the same `GOST3411-2012-256` function
(def d4 (d/digest-2012-256 "dev/src/examples/plain32.txt"))

(common/bytes-to-hex d4)                                    ;; =>
;; "ee363d5e40c1ff1965ee308beef1ca153c1d56d377a63be29924731732f2c697"

;; To generate GOST3411-2012-512 digest from byte array use `digest-2012-512` function
(def d5 (d/digest-2012-512 (.getBytes message)))

(common/bytes-to-hex d5)                                    ;; =>
;; "d2b793a0bb6cb5904828b5b6dcfb443bb8f33efc06ad09368878ae4cdc8245b97e60802469bed1e7c21a64ff0b179a6a1e0bb74d92965450a0adab69162c00fe"

;; To generate GOST3411-2012-512 digest from file use the same `GOST3411-2012-512` function
(def d6 (d/digest-2012-512 "dev/src/examples/plain32.txt"))

(common/bytes-to-hex d6)                                    ;; =>
;; "7f75cf439c41420b25a3964ab0608af592c9af44e852dcbc18ae9fcfa0c2d7e3edda83715d23d30e5d3dc521290c66980695faa69adc7c5854ced01f0af6f0e9"

HMAC

This is high-level functions.

hmac.clj
(require '[org.rssys.gost.digest :as d])
(require '[org.rssys.gost.encrypt :as e])

(def message "The quick brown fox jumps over the lazy dog")

;; generate secret key bytes from password
(def secret-key (e/generate-secret-bytes-from-password "12345678"))

;; Generate HMAC using GOST3411-94 and secret-key bytes
(def h1 (d/hmac-3411-94 (.getBytes message) secret-key))

(common/bytes-to-hex h1)                                    ;; =>
;; "1ffb045ab775c674b5809d6f5c180c73be459223e93951e8c19cc1e0ed559b20"

;; Generate HMAC using GOST3411-2012-256 and secret-key bytes
(def h2 (d/hmac-2012-256 (.getBytes message) secret-key))

(common/bytes-to-hex h2)                                    ;; =>
;; "405854baba2cc90661f1ff08e40c2cd0fb36869a5a32f655f51ea6fd577c6d84"

;; Generate HMAC using GOST3411-2012-512 and secret-key bytes
(def h3 (d/hmac-2012-512 (.getBytes message) secret-key))

(common/bytes-to-hex h3)                                    ;; =>
;; "14923d761858aa272028855999c0bd3f37964e98bb3bb163825ecfbcd049e10f612566053031bec01611bc9584ef24aa80073cecc51d125fe989a973dd1f6813"

;; To generate GOST3411-2012-256 HMAC from file use the same `hmac-2012-256` function
(def h4 (d/hmac-2012-256 "dev/src/examples/plain32.txt" secret-key))

(common/bytes-to-hex h4)                                    ;; =>
;; "2c36afad546eb7026b1bfd92dc83a6e6cfd20f301a786fed41fd3c2213214d43"

Signature

This is high-level functions.

sign.clj
(require '[org.rssys.gost.sign :as s])

(def message "This is a message.")

;; Generate public and private keypair 256 bit length
(def kp-256 (s/gen-keypair-256))

(def public-key-256 (.getPublic kp-256))
(def private-key-256 (.getPrivate kp-256))

;; Generate signature for byte array.
;; Digest GOST3411-2012 256-bit length for message will be calculated automatically.
(def signature-256 (s/sign-256 private-key-256 (.getBytes message)))

;; Check signature length
(alength signature-256)                                     ;; => 64

;; Check signature using public key
(s/verify-256 public-key-256 (.getBytes message) signature-256) ;; => true

;; Generate public and private keypair 512 bit length
(def kp-512 (s/gen-keypair-512))

(def public-key-512 (.getPublic kp-512))
(def private-key-512 (.getPrivate kp-512))

;; Generate signature for a file.
;; Digest GOST3411-2012 512 bit length for file content will be calculated automatically.
(def signature-512 (s/sign-512 private-key-512 "test/data/big.txt"))

;; Check signature length
(alength signature-512)                                     ;; => 128

;; Check signature of file using public key
(s/verify-512 public-key-512 "test/data/big.txt" signature-512) ;; => true

Export / Import Keys and other data to PEM

This is high-level functions.

pem.clj
(require '[org.rssys.gost.sign :as s])
(require '[org.rssys.gost.pem :as p])
(require '[org.rssys.gost.encrypt :as e])

;; Generate secret key
(def secret-key-2015 (e/generate-secret-key))

;; Convert SecretKeySpec to encrypted PEM string
;; Secret key will be encrypted with key derived from PBKDF2(`password`) using GOST3412-2015-CBC"
(def encrypted-pem-secret-key (p/secret-key->encrypted-pem secret-key-2015 "123456"))


;; Restore secret key from encrypted PEM
;; Secret key will be decrypted with key derived from PBKDF2(`password`) using GOST3412-2015-CBC"
(def restored-encrypted-secret-key-2015 (p/encrypted-pem->secret-key encrypted-pem-secret-key "123456"))


;; Check that keys are equal
(= restored-encrypted-secret-key-2015 secret-key-2015)

;; PEM string with plain secret key
;; Warning: PEM string will be not encrypted!
(def pem-secret-key-2015 (p/secret-key->pem secret-key-2015))


;; Restore secret key from plain PEM
(def restored-secret-key-2015 (p/pem->secret-key pem-secret-key-2015))


;; Check that keys are equal
(= restored-secret-key-2015 secret-key-2015)

;; Generate public and private keypair 256 bit length
(def kp-256 (s/gen-keypair-256))

(def public-key-256 (.getPublic kp-256))
(def private-key-256 (.getPrivate kp-256))

;; To save private key to encrypted PEM (PKCS8) string use `private-key->encrypted-pem`
;; Private key will be encrypted with AES-256-CBC in `openssl` format.
(p/private-key->encrypted-pem private-key-256 "123456")     ;;=>
;; "-----BEGIN ENCRYPTED PRIVATE KEY-----
;;MIGpMFUGCSqGSIb3DQEFDTBIMCcGCSqGSIb3DQEFDDAaBBSMtRpFQ6n7RgZTriGG
;;bFr8JJeKiQICBAAwHQYJYIZIAWUDBAEqBBB0XmFK1rvMBnC4t7BSGndLBFDiON0S
;;e1iGKb80u/lLXti1+7x9QKCGZtVIJw62YIQWAxy7zK5vZ1xAQxSRNjssfi0niroL
;;0ZqRJpFb6czeCFyq2HBzUvNH2rRdAiRv91KDgg==
;;-----END ENCRYPTED PRIVATE KEY-----
;;"

;; To restore private key from encrypted PEM (PKCS8) string use `encrypted-pem->private-key`
(p/encrypted-pem->private-key (slurp "test/data/test-encrypted-private-key.pem") "123456") ;;=>
;;#object[org.bouncycastle.jcajce.provider.asymmetric.ecgost12.BCECGOST3410_2012PrivateKey 0x3776cb5 "ECGOST3410-2012
;; Private Key [55:07:ef:03:d1:7f:ea:e7:53:ca:58:6d:0e:da:0a:6f:e2:93:4b:b4]
;;            X: df0679d81ec2156f062b507918c10fb9e680848be92ec69af6be9f32ffd8669e
;;            Y: 2234280a15135e723579ef96544742f6cc06f8d59cccd88fd4b377f818ce9f95
;;"]


;; Convert PrivateKey to PEM string
;; Warning: PEM string will be not encrypted!
(def private-pem-256 (p/private-key->pem private-key-256))


;; Convert PublicKey to PEM string
(def public-pem-256 (p/public-key->pem public-key-256))


;; Convert PEM string to a PrivateKey
(def restored-private-256 (p/pem->private-key private-pem-256))


;; check that keys are equal
(= restored-private-256 private-key-256)


;; Convert PEM string to a PublicKey
(def restored-public-256 (p/pem->public-key public-pem-256))


;; check that keys are equal
(= restored-public-256 public-key-256)


;; You can write to PEM format arbitrary byte array
(p/write-bytes-to-pem "MESSAGE" (.getBytes "Hello"))

;; You can read from PEM arbitrary byte array
(String. (p/read-bytes-from-pem "-----BEGIN MESSAGE-----\nSGVsbG8=\n-----END MESSAGE-----\n"))

;; You can write structured data to PEM format
(p/write-struct-to-pem {:data (.getBytes "Hello") :type "MESSAGE" :headers {:status "unencrypted" :date "01-01-2022"}} ) ;; =>
;; "-----BEGIN MESSAGE-----
;;status: unencrypted
;;date: 01-01-2022
;;
;;SGVsbG8=
;;-----END MESSAGE-----
;;"

;; You can read structured data from PEM string
(p/read-struct-from-pem (slurp "test/data/test-plain-with-headers.pem")) ;; =>
;;{:data [72, 101, 108, 108, 111],
;; :type "MESSAGE",
;; :headers {"status" "unencrypted", "date" "01-01-2022"}}

Also, you can use openssl with GOST support to read PEM private key. Download openssl with GOST from here: docker run --rm -i -t rnix/openssl-gost bash.

openssl.sh
openssl pkey -in test/data/test-private-key.pem -noout -text
Private key: DC03D4325299B33F75DFE365E3018330B72FE4FE227A00509D92EDC43034D3106F98F6E8B9CA71D2620DE1DF329549860688713DF97D7FE3CA118C7BB74290CB
Public key:
   X:583E506A840B067967A9C66AC5DE4E55F9C179E723E7D5FD9E5B3C474413416CA9EBB62202433A4DE92DE8B711619AC69F18ED35559563D2563F451C13128C2E
   Y:192D0CE9E4A62EF00CF4E523C429B7A18EB55CF52DC6F1D76FCF6F5599F2C112D7650BC7501B2C4E4D51E4A597B9B9C157B9F2C71098D9F3A8348A17F0769488
Parameter set: id-tc26-gost-3410-2012-512-paramSetA
Digest Algorithm: GOST R 34-11-2012 (512 bit)

openssl pkey -pubin -in test/data/test-public-key.pem -text
-----BEGIN PUBLIC KEY-----
MIGqMCEGCCqFAwcBAQECMBUGCSqFAwcBAgECAQYIKoUDBwEBAgMDgYQABIGALowS
ExxFP1bSY5VVNe0Yn8aaYRG36C3pTTpDAiK266lsQRNERzxbnv3V5yPnecH5VU7e
xWrGqWd5BguEalA+WIiUdvAXijSo89mYEMfyuVfBubmXpeRRTU4sG1DHC2XXEsHy
mVVvz2/X8cYt9Vy1jqG3KcQj5fQM8C6m5OkMLRk=
-----END PUBLIC KEY-----
Public key:
   X:583E506A840B067967A9C66AC5DE4E55F9C179E723E7D5FD9E5B3C474413416CA9EBB62202433A4DE92DE8B711619AC69F18ED35559563D2563F451C13128C2E
   Y:192D0CE9E4A62EF00CF4E523C429B7A18EB55CF52DC6F1D76FCF6F5599F2C112D7650BC7501B2C4E4D51E4A597B9B9C157B9F2C71098D9F3A8348A17F0769488
Parameter set: id-tc26-gost-3410-2012-512-paramSetA
Digest Algorithm: GOST R 34-11-2012 (512 bit)

Armor data

This is high-level functions.

armor.clj
(require '[org.rssys.gost.armor :as a])
(require '[org.rssys.gost.pem :as p])
(require '[org.rssys.gost.sign :as s])

;; Read test public and private keys
(def public-key-256 (p/pem->public-key (slurp "test/data/test-public-key-256.pem")))
(def private-key-256 (p/pem->private-key (slurp "test/data/test-private-key-256.pem")))


;; Read plain text
(def plain-32-message (slurp "test/data/plain32.txt"))


;; Sign message + time and produce armored message: plain text + time + signature
(def armored-message (a/sign-message private-key-256 plain-32-message))

"-----[ START DATA ]-----
This is message, length=32 bytes
-----[ END DATA ]-----
-----BEGIN SIGNATURE-----
time: 2022-02-24T01:23:16.246966

Ow1R7KEKvFjbOg33LZt390NqPX2d6vOQi3k0lNWDMJ8aJGlOhLvYxfZlk8FnpbKI
eUsdGvkNc4yzwM5x5VEwRQ==
-----END SIGNATURE-----
"

;; Verify signature for message + time and if signature is valid return message
(def restored-message (a/verify-message public-key-256 armored-message))

;; Sign message with headers data
(def armored-message-with-headers
  (a/sign-message private-key-256 plain-32-message
    :headers {:issuer "Certification Authority" :address "Moscow"}))

"-----[ START DATA ]-----
This is message, length=32 bytes
-----[ END DATA ]-----
-----BEGIN SIGNATURE-----
issuer: Certification Authority
address: Moscow
time: 2022-02-24T01:38:20.397299

IiCHULQELZsdRFcsbjE9Xm0c24fvXplOiUO1mL4ze+wcPASsRX2ouA7L52xb7gZe
WLxZ2OKplo+0JHVyXQCXRQ==
-----END SIGNATURE-----
"

;; Sign message with headers data and custom date-time formatter
(def armored-message-with-headers-custom-formatter
  (a/sign-message private-key-256 plain-32-message
    :headers {:issuer "Certification Authority" :address "Moscow"}
    :datetime-formatter (DateTimeFormatter/ofLocalizedDateTime FormatStyle/LONG)))
"-----[ START DATA ]-----
This is message, length=32 bytes
-----[ END DATA ]-----
-----BEGIN SIGNATURE-----
issuer: Certification Authority
address: Moscow
time: 24 февраля 2022 г., 23:26:26 MSK

OFVM4pfFJ4hpC0p7Scr9XNAj1JDvf3meDGWFTNIhWsgyKCIfGJc7FGyDsmFPzvlY
OgkYN1rBj8kgIQDvrM+W7A==
-----END SIGNATURE-----
"

;; Verify message with headers
(a/verify-message public-key-256 (slurp "test/data/armored-plain32-with-headers.pem"))

Low-level functions

Use these functions carefully.
If you are not sure use high-level functions only!

IV & Mac length

;; IV length depends on encryption mode and algorithm
(e/iv-length-by-algo-mode e/gost3412-2015 :cfb-mode)        ;; => 16
(e/iv-length-by-algo-mode e/gost3412-2015 :cbc-mode)        ;; => 16
(e/iv-length-by-algo-mode e/gost3412-2015 :ctr-mode)        ;; => 8 !!

(e/iv-length-by-algo-mode e/gost28147 :cfb-mode)        ;; => 8
(e/iv-length-by-algo-mode e/gost28147 :cbc-mode)        ;; => 8
(e/iv-length-by-algo-mode e/gost28147 :ctr-mode)        ;; => 8

;; Mac length
(e/mac-length-by-algo e/gost3412-2015)                      ;; => 16
(e/mac-length-by-algo e/gost28147)                          ;; => 4

Encryption modes

This is for low-level functions.

Available encryption modes for GOST3412-2015:

  • :cfb-mode is "GOST3412-2015/CFB/NoPadding"

  • :ctr-mode is "GOST3412-2015/CTR/NoPadding"

  • :cbc-mode is "GOST3412-2015/CBC/PKCS7Padding"

Available encryption modes for GOST28147:

  • :cfb-mode is "GOST28147/CFB/NoPadding"

  • :ctr-mode is "GOST28147/CTR/NoPadding"

  • :cbc-mode is "GOST28147/CBC/PKCS7Padding"

Init Cipher Mode

;; Init Cipher for GOST28147 in CFB, CTR, CBC mode
(def cipher1 (e/init-cipher-mode e/gost28147 :cfb-mode))
(def cipher2 (e/init-cipher-mode e/gost28147 :ctr-mode))
(def cipher3 (e/init-cipher-mode e/gost28147 :cbc-mode))

;; Init Cipher for GOST3412-2015 in CFB, CTR, CBC mode
(def cipher4 (e/init-cipher-mode e/gost3412-2015 :cfb-mode))
(def cipher5 (e/init-cipher-mode e/gost3412-2015 :ctr-mode))
(def cipher6 (e/init-cipher-mode e/gost3412-2015 :cbc-mode))

Create random IV

This is low-level functions.

;; Random IV generation

(e/new-iv-8)                                                ;; => [B
;; [25, 117, -36, -32, -87, -128, -25, 23]

(e/new-iv-16)                                               ;;=> [B
;; [29, -49, 83, 120, -125, 95, 41, -54, -11, -37, -2, -19, 123, -122,
;; -21, 6]

;; Also we can generate IV depend on cipher mode and algorithm name
(e/new-iv e/gost28147 :cfb-mode)                            ;; => [B
;; [-101, 29, 29, 55, 112, 14, 55, 104]

(e/new-iv e/gost3412-2015 :cbc-mode)                        ;; => [B
;; [6, 87, 96, -83, -128, 25, -57, -70, -54, 51, 9, -26, 73, -103, 64, 67]

;; Warning! IV for :ctr-mode is always 8 bytes length for any algorithm
(e/new-iv e/gost3412-2015 :ctr-mode)                        ;; => [B => [45, -71, 116, -67, 9, -39, -101, -51]
(e/new-iv e/gost28147 :ctr-mode)                            ;; => [B => [8, 39, -126, -5, 122, -120, 1, -108]

Init Cipher

This is low-level functions.

GOST named parameters

The GOST28147-89 has several named S-box parameters:

  • "E-A" - Gost28147_89_CryptoPro_A_ParamSet (most used);

  • "E-B" - Gost28147_89_CryptoPro_B_ParamSet (most used);

  • "E-C" - Gost28147_89_CryptoPro_C_ParamSet;

  • "E-D" - Gost28147_89_CryptoPro_D_ParamSet;

  • "Param-Z" - tc26_gost_28147_param_Z.

(def secret-key (e/generate-secret-key e/gost28147))        ;; generate secret key
(def iv-8 (e/new-iv (e/algo-name secret-key) :cfb-mode))      ;; generate new random IV
(def algo-param-spec (e/init-gost-named-params (e/algo-name secret-key) iv-8 "E-A")) ;; Init GOST with "E-A" parameters

GOST OID parameters

The GOST28147-89 has several OID S-box parameters OID params table

;; Init GOST with OID parameters
;; See https://cpdn.cryptopro.ru/content/csp40/html/group___pro_c_s_p_ex_CP_PARAM_OIDS.html
(e/init-gost-oid-params e/gost28147 iv-8 (org.bouncycastle.asn1.ASN1ObjectIdentifier. "1.2.643.2.2.31.1"))

GOST binary S-box parameters

The GOST28147-89 may be initialized with 'S-boxes' as bytes array:

;; Init GOST 28147 with S-box as binary array
;; https://datatracker.ietf.org/doc/html/rfc4357
;; id-Gost28147-89-CryptoPro-A-ParamSet
(def ^:const s-box-crypto-pro-a
  [9 6 3 2 8 11 1 7 10 4 14 15 12 0 13 5
   3 7 14 9 8 10 15 0 5 2 6 12 11 4 13 1
   14 4 6 2 11 3 13 8 12 15 5 10 0 7 1 9
   14 7 10 12 13 1 3 9 0 2 11 4 15 8 5 6
   11 5 1 9 8 13 15 0 14 4 2 3 12 7 10 6
   3 10 13 12 1 2 0 11 7 5 9 4 8 15 14 6
   1 13 2 9 7 10 6 0 8 12 4 5 15 3 11 14
   11 10 15 5 0 12 14 8 6 2 3 9 1 7 13 4])

(e/init-gost-sbox-binary-params e/gost28147 iv-8 (byte-array s-box-crypto-pro-a))

Encryption mode

The new-encryption-cipher is a low-level function.

;; Init cipher for GOST3412-2015,  generate random IV automatically
(def cipher-2015 (e/new-encryption-cipher secret-key-2015 :cfb-mode))
;; extract IV
(.getIV cipher-2015)                                             ;; => [B
;;[105, 13, 115, 71, 2, -23, 6, 82, -30, -13, 113, -12, -34, 69, -6, 27]

;; Init cipher for GOST28147,  generate random IV automatically
(def cipher-89 (e/new-encryption-cipher secret-key-89 :cfb-mode))
;; extract IV
(.getIV cipher-89) ;; => [-84, -116, -60, -99, 89, 43, -107, 127]


;; Init cipher for GOST3412-2015,  with AlgoParamsSpec, IV should be always random
(def cipher-2015 (e/new-encryption-cipher secret-key-2015 :cfb-mode
                   (javax.crypto.spec.IvParameterSpec. (e/new-iv-16))))

;; Init cipher for GOST28147,  with AlgoParamsSpec, IV should be always random
(def cipher-89 (e/new-encryption-cipher secret-key-89 :cfb-mode
                 (e/init-gost-named-params (e/algo-name secret-key-89) (e/new-iv-8) "E-A")))

Decryption mode

The new-decryption-cipher is a low-level function.

;; Init decryption cipher for GOST3412-2015
(def iv-16 (.getIV cipher-2015))            ;; we should use the same IV which was used in encryption phase
(def decryption-cipher-2015 (e/new-decryption-cipher secret-key-2015 :cfb-mode
                              (javax.crypto.spec.IvParameterSpec. iv-16)))

;; Init decryption cipher for GOST28147
;; we should use the same IV and S-boxes which were used in encryption phase
(def iv-8 (.getIV cipher-89))
(def decryption-cipher-89 (e/new-decryption-cipher secret-key-89 :cfb-mode
                            (e/init-gost-named-params (e/algo-name secret-key-89) iv-8 "E-A")))

Encryption/Decryption

This is low-level functions.

enc.clj
;; Init cipher for GOST3412-2015,  generate random IV automatically
(def cipher-2015 (e/new-encryption-cipher secret-key-2015 :cfb-mode))
(def iv-16 (.getIV cipher-2015))
(def decryption-cipher-2015 (e/new-decryption-cipher secret-key-2015 :cfb-mode (javax.crypto.spec.IvParameterSpec. iv-16)))

;; To encrypt bytes use `encrypt-bytes` function and Cipher initialized with
;; secret key and random IV in encryption mode
(def e1 (e/encrypt-bytes cipher-2015 (.getBytes message)))  ;; => [B
;;[79, 67, 111, -67, 4, 99, 92, -68, 66, -35, 77, -6, 115, 56, 108, 47,
;; -124, -82, 107, -18, -95, -125, -18, 106, -53, -21, 0, -108, -48, 41,
;; -86, -84]

;; Remember, you should know IV which was used during encryption to decrypt it.

;; To decrypt bytes use `decrypt-bytes` function and Cipher initialized with
;; the same secret key and the same IV in decryption mode
(String. ^bytes (e/decrypt-bytes decryption-cipher-2015 e1)) ;; => "This text has length = 32 bytes."


;; To encrypt file use `encrypt-stream` function and Cipher initialized with
;; secret key and random IV in encryption mode
(e/encrypt-stream cipher-2015 "dev/src/examples/plain32.txt" "target/plain32.enc")


;; Remember, you should know IV which was used during encryption to decrypt it.

;; To decrypt file use `decrypt-stream` function and Cipher initialized with
;; the same secret key and the same IV in decryption mode
(e/decrypt-stream decryption-cipher-2015 "target/plain32.enc" "target/plain32.txt") ;; => "This text has length = 32 bytes."

(slurp "target/plain32.txt") ;; => "This text has length = 32 bytes."

Compression / Decompression

This is low-level functions.

comp-decomp.clj
;; To compress plain bytes to hide its internal structure before encryption use `compress-bytes` function
(def cb (e/compress-bytes (.getBytes message)))                    ;; => [B
;;[120, -38, 11, -55, -56, 44, 86, 40, 73, -83, 40, 81, -56, 72, 44, 86,
;; -56, 73, -51, 75, 47, -55, 80, -80, 85, 48, 54, 82, 72, -86, 44, 73,
;; 45, -42, 3, 0, -71, 112, 10, -45]

;; To decompress plain bytes use `decompress-bytes` function
(String. (e/decompress-bytes cb))                   ;; => "This text has length = 32 bytes."

;; To compress file to hide its internal structure before encryption use `compress-stream` function
(e/compress-stream "dev/src/examples/plain32.txt" "target/plain32.gz")

;; To decompress file use `decompress-stream` function
(e/decompress-stream "target/plain32.gz" "target/plain32.txt")

(slurp "target/plain32.txt") ;; => "This text has length = 32 bytes."

Compression+Encryption / Decryption + Decompression

This is low-level functions.

comp-e.clj
;; Init cipher for GOST3412-2015,  generate random IV automatically
(def cipher-2015 (e/new-encryption-cipher secret-key-2015 :cfb-mode))
(def iv-16 (.getIV cipher-2015))
(def decryption-cipher-2015 (e/new-decryption-cipher secret-key-2015 :cfb-mode (javax.crypto.spec.IvParameterSpec. iv-16)))

(def baos (ByteArrayOutputStream.))


;; To compress  and encrypt plain bytes use `compress-and-encrypt-stream` function
(e/compress-and-encrypt-stream cipher-2015 (.getBytes message) baos)
(def ceb (.toByteArray baos)) ;; => [B
;;[-14, -43, -92, -4, -79, 85, 72, -50, 77, -102, -24, 21, -119, 81,
;; -100, -98, 39, -79, -56, 61, -95, 118, 28, -126, 39, -65, 10, -15, 21,
;; -33, 23, -44, 60, 52, 76, 35, 69, 119, -96, 50]

(def baosd (ByteArrayOutputStream.))

;; Remember, you should know IV which was used during encryption to decrypt it.

;; To decompress and decrypt use `decrypt-and-decompress-stream` function
(e/decrypt-and-decompress-stream decryption-cipher-2015 ceb baosd)
(String. (.toByteArray baosd)) ;; => "This text has length = 32 bytes."

;; To compress and encrypt file  use `compress-and-encrypt-stream` function
(e/compress-and-encrypt-stream cipher-2015 "dev/src/examples/plain32.txt" "target/plain32.egz")

;; Remember, you should know IV which was used during encryption to decrypt it.

;; To decompress and decrypt file use `decrypt-and-decompress-stream` function
(e/decrypt-and-decompress-stream decryption-cipher-2015 "target/plain32.egz" "target/plain32.txt")

(slurp "target/plain32.txt") ;; => "This text has length = 32 bytes."

Build this project

Project org.rssys/gost generated from Library template.

Install prerequisites

All these tools you need to install only once.

  1. Install clojure deps cli tools version 1.10.3.1069+

    1. MacOS

      brew install clojure/tools/clojure
    2. Linux

      Ensure that the following dependencies are installed in OS: bash, curl, rlwrap, and Java.

      curl -O https://download.clojure.org/install/linux-install-1.10.3.1069.sh
      chmod +x linux-install-1.10.3.1069.sh
      sudo ./linux-install-1.10.3.1069.sh
  2. Install latest deps-new

    clojure -Ttools install io.github.seancorfield/deps-new '{:git/tag "v0.4.9"}' :as new

    Tool will be installed in ~/.gitlibs/libs/

  3. Install babashka v0.4.0+

    1. MacOS

      brew install borkdude/brew/babashka
    2. Linux

      sudo bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install)
  4. Run once:

    bb requirements

    to install other necessary tools (MacOS only, for Linux manual instruction).

Project workflow

To configure project workflow scripts use bb.edn and build.clj files.

Run bb tasks to show help for project workflow. The following tasks are available:

clean        Clean target folder
build        Build deployable jar file for this project
install      Install deployable jar locally (requires the pom.xml file)
deploy       Deploy this library to Clojars
test         Run tests
repl         Run Clojure repl
outdated     Check for outdated dependencies
outdated:fix Check for outdated dependencies and fix
format       Format source code
lint         Lint source code
requirements Install project requirements

License

Copyright © 2022 Mike Ananev
Distributed under the Eclipse Public License 1.0 or (at your option) any later version.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close