├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.sbt
├── project
├── build.properties
└── plugins.sbt
└── src
├── main
└── scala
│ ├── BlockCipher.scala
│ ├── BlockCipherMode.scala
│ ├── BlockCipherSuite.scala
│ ├── BlockPadding.scala
│ ├── Exceptions.scala
│ ├── Hash.scala
│ ├── Key.scala
│ ├── KeyedHash.scala
│ ├── Parameters.scala
│ ├── Random.scala
│ ├── blockciphers
│ ├── RSA.scala
│ ├── SymmetricJavaBlockCipher.scala
│ └── Threefish.scala
│ ├── hash
│ ├── MD5.scala
│ ├── MDConstruction.scala
│ ├── SHA1.scala
│ └── SHA256.scala
│ ├── iteratees
│ ├── Enumeratee.scala
│ ├── Enumerator.scala
│ └── Iteratee.scala
│ ├── keys
│ ├── RSAKey.scala
│ └── SymmetricKey.scala
│ ├── khash
│ ├── Hmac.scala
│ ├── PBKDF2.scala
│ └── RSASSA_PSS.scala
│ ├── modes
│ ├── CBC.scala
│ └── ECB.scala
│ ├── package.scala
│ ├── paddings
│ ├── NoPadding.scala
│ ├── OAEP.scala
│ └── PKCS7Padding.scala
│ ├── suites
│ ├── AES_CBC_NoPadding.scala
│ ├── AES_CBC_PKCS7Padding.scala
│ ├── RSAES_OAEP.scala
│ └── Threefish_CBC_PKCS7Padding.scala
│ └── util
│ └── PBKDF2Easy.scala
└── test
└── scala
├── BlockCipherSpec.scala
├── BlockPaddingSpec.scala
├── HashSpec.scala
├── IterateeSpec.scala
├── KeyedHashSpec.scala
├── PBKDF2Spec.scala
├── RSASpec.scala
├── RichBigIntSpec.scala
├── SymmetricBlockCipherSuiteSpec.scala
├── SymmetricKeySpec.scala
└── ThreefishSpec.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | project/project
3 | *.swp
4 | *.swo
5 | .idea
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | scala:
3 | - 2.12.2
4 | dist: trusty
5 | jdk:
6 | - openjdk10
7 | script: "sbt clean coverage test"
8 | after_success: "sbt coverageReport coveralls"
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Scalacrypt
2 | ==========
3 |
4 | [](https://travis-ci.org/Richard-W/scalacrypt)
5 | [](https://coveralls.io/r/Richard-W/scalacrypt)
6 |
7 | Scalacrypt provides advanced cryptographic functions for scala projects. It wraps the
8 | javax.crypto API and provides a few things that are not implemented there in a way
9 | usable for this project.
10 |
11 | This project is under heavy development and not suited for production use!!!
12 |
13 | To add scalacrypt to your sbt project just add the following line to your build.sbt:
14 |
15 | ```scala
16 | libraryDependencies += "xyz.wiedenhoeft" %% "scalacrypt" % "0.4.0"
17 | ```
18 |
19 | You can use the current snapshot by putting the following lines in your build.sbt:
20 |
21 | ```scala
22 | resolvers += Resolver.sonatypeRepo("snapshots")
23 |
24 | libraryDependencies += "xyz.wiedenhoeft" %% "scalacrypt" % "0.5-SNAPSHOT"
25 | ```
26 |
27 | As the API is subject to heavy changes i recommend you use "sbt doc" to get definitive reference.
28 | Im doing my best to keep the documentation and this README up-to-date but as long as i did not
29 | stabilize the API it might sometimes be a little off.
30 |
31 | Symmetric encryption
32 | --------------------
33 |
34 | Symmetric encryption in scalacrypt is achieved by combining a BlockCipher, a BlockPadding and a BlockCipherMode.
35 | These objects can be combined inside a BlockCipherSuite object which drives the encryption and calls the appropriate
36 | methods on the primitives. It provides the encrypt and the decrypt methods.
37 |
38 | Example for constructing a BlockCipherSuite. You have to make sure yourself that the IV is valid.
39 | ```scala
40 | import blockciphers.AES128
41 | import modes.CBC
42 | import paddings.PKCS7Padding
43 |
44 | val params = Parameters(
45 | 'symmetricKey128 -> Key.generate[SymmetricKey128],
46 | 'iv -> Random.nextBytes(16)
47 | )
48 |
49 | // When you dynamically build the params object you might want to match the
50 | // Try objects for error handling
51 | val aes = BlockCipher[AES128](params).get
52 | val cbc = BlockCipherMode[CBC](params).get
53 | val pkcs7 = BlockPadding[PKCS7Padding](params).get
54 |
55 | val suite = new BlockCipherSuite(aes, cbc, pkcs7)
56 | ```
57 |
58 | There are certain helper functions in the 'suites' package. They automatically validate parameters and return a Try.
59 |
60 | ```scala
61 | val suite = suites.AES128_CBC_PKCS7Padding(Key.generate[SymmetricKey128], None).get
62 | val iv = suite.params('iv)
63 | val key = suite.params('symmetricKey128)
64 | ```
65 |
66 | KeyType is a specific child of Key. For AES256 it is SymmetricKey256 for example.
67 | You get the idea. The predefined key classes can be instantiated using the following
68 | methods:
69 |
70 | ```scala
71 | // Using implicit conversion to MightBuildKeyOp
72 | val specificKey = (0 until 16 map { _.toByte }).toSeq.toKey[SymmetricKey128].get
73 | // If the supplied key is invalid toKey will return a Failure and get will throw. When
74 | // you can't guarantee the validity of the key use pattern matching.
75 |
76 |
77 | val randomKey = Key.generate[SymmetricKey128]
78 | ```
79 |
80 | When you define own subclasses of Key you should also define appropriate implicit implementations of CanGenerateKey
81 | and MightBuildKey.
82 |
83 | When you have created a suite you can use the encrypt/decrypt method to encrypt/decrypt an Iterator[Seq[Byte]] to an Iterator[Try[Seq[Byte]]].
84 | If the resulting iterator contains a single Failure encryption or decryption must be aborted. There are helper methods for processing a single
85 | Seq[Byte] to a single Try[Seq[Byte]]. These helper methods overload encrypt and decrypt.
86 |
87 | Asymmetric encryption
88 | ---------------------
89 |
90 | Since version 0.4 scalacrypt contains an RSA implementation. You can generate a key just like a symmetric key.
91 | The key type is RSAKey. An RSAKey can be either public or private. Internally there are two types of private keys
92 | which essentially do the same but with different computational efficiency (see Chinese Remainder Theorem).
93 |
94 | ```scala
95 | val privateKey = Key.generate[RSAKey]
96 | val publicKey = privateKey.publicKey
97 | ```
98 |
99 | RSA keys can be stored using the output of key.bytes. The resulting sequence of bytes can be restored to a key
100 | using its toKey[RSAKey] method. The binary format used for serializing the keys is specific to scalacrypt and
101 | sadly not compatible with anything. This will most likely change.
102 |
103 | There is exactly one cipher suite available for RSA encryption: RSAES\_OAEP which implements message encryption
104 | according to PKCS#1. Usage is equivalent to the symmetric cipher suites except decryption will return a Failure
105 | when the private key is unavailable.
106 |
107 | Message authentication
108 | ----------------------
109 |
110 | The KeyedHash trait provides an interface for various methods for authenticating message.
111 |
112 | ```scala
113 | import khash.HmacSHA256
114 |
115 | val message = "Hello world!".getBytes
116 | val falseMessage = "Bye world!".getBytes
117 |
118 | val hmacKey = "somepassword".getBytes.toSeq.toKey[SymmetricKeyArbitrary].get
119 | val mac = HmacSHA256(hmacKey, message).get
120 |
121 | println(HmacSHA256.verify(hmacKey, message, mac).get) //prints true
122 | println(HmacSHA256.verify(hmacKey, falseMessage, mac).get) //prints false
123 | ```
124 |
125 | Also an RSASSA-PSS signing algorithm is implemented using the KeyedHash trait:
126 |
127 | ```scala
128 | import khash.RSASSA_PSS
129 | import hash.SHA256
130 |
131 | val privateKey = Key.generate[RSAKey]
132 | val publicKey = privateKey.publicKey
133 |
134 | val message = "Hello World".getBytes
135 | val falseMessage = "Bye world!".getBytes
136 |
137 | val signer = RSASSA_PSS(SHA256, 32)
138 | val signature = signer(privateKey, message).get
139 |
140 | println(signer.verify(publicKey, message, signature).get) // Prints true
141 | println(signer.verify(publicKey, falseMessage, signature).get) // Prints false
142 | ```
143 |
144 | Password hashing
145 | ----------------
146 |
147 | This library contains the util.PBKDF2Easy object which securely hashes a Seq[Byte] using PBKDF2. The used parameters and the salt are encoded
148 | in the resulting Seq[Byte]. It is safe to save the result to a database.
149 |
150 | If you want to use the pure PBKDF2 for other purposes than password hashing you can use khash.PBKDF2 which generates KeyedHash objects.
151 | The data supplied to this KeyedHash is used as the salt.
152 |
153 | Contributing
154 | ------------
155 |
156 | * Bug reports are appreciated as much as actual code contributions. Do not hesitate to report if you encounter a problem.
157 | * All parts of this library MUST never throw exceptions. Functions should return a Try if they might fail. Also if you encounter an exception i consider it a bug and would appreciate if you reported it here.
158 | * The library should be kept extensible. It MUST not be necessary to contribute to this library to implement new algorithms. However if you think an algorithm might be of use for others do not hesitate to merge it.
159 | * This project was born out of necessity. There seems to be no other project in scala providing this functionality and i needed it. I am no cryptography expert but i read a few articles about best practices for encryption. You are welcome to tell me where i am wrong. In fact i will not consider this project stable until a few people who **really** know what they are doing reviewed it.
160 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | lazy val root = project.in(file("."))
2 | .aggregate(scalacrypt)
3 | .settings(
4 | publish := {},
5 | publishLocal := {},
6 | publishTo := Some(Resolver.file("Unused transient repository", file("target/unusedrepo")))
7 | )
8 |
9 | lazy val scalacrypt = project.in(file("."))
10 | .settings(
11 | name := "scalacrypt",
12 | organization := "xyz.wiedenhoeft",
13 | version := "0.5-SNAPSHOT",
14 | licenses := Seq("Apache" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
15 | homepage := Some(url("https://github.com/richard-w/scalacrypt")),
16 | scalaVersion := "2.12.8",
17 | scalacOptions ++= Seq("-feature", "-unchecked", "-deprecation", "-opt:_"),
18 | libraryDependencies ++= {
19 | Seq(
20 | "org.scalatest" %% "scalatest" % "3.0.5"
21 | )
22 | },
23 | publishMavenStyle := true,
24 | publishTo := {
25 | val nexus = "https://oss.sonatype.org/"
26 | if (isSnapshot.value)
27 | Some("snapshots" at nexus + "content/repositories/snapshots")
28 | else
29 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
30 | },
31 | publishArtifact in Test := false,
32 | useGpg := true,
33 | usePgpKeyHex("CB8F8B69"),
34 | pomExtra := (
35 |
36 |
37 | richard-w
38 | Richard Wiedenhöft
39 | https://github.com/Richard-W
40 |
41 |
42 |
43 | https://github.com/richard-w/scalacrypt
44 | scm:https://github.com/richard-w/scalacrypt.git
45 |
46 | )
47 | )
48 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.8
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
2 |
3 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
4 |
5 | addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7")
6 |
7 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2")
8 |
--------------------------------------------------------------------------------
/src/main/scala/BlockCipher.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import scala.util.{ Try, Success, Failure }
18 |
19 | /** Base trait for symmetric block ciphers such as AES. */
20 | trait BlockCipher[KeyType <: Key] {
21 |
22 | /** Parameters used to construct this cipher. */
23 | def params: Parameters
24 |
25 | /** Block size in bytes. */
26 | def blockSize: Int
27 |
28 | /** The key used for en- and decryption. */
29 | def key: KeyType
30 |
31 | /** Returns a function that encrypts single blocks using the key. */
32 | def encryptBlock(block: Seq[Byte]): Try[Seq[Byte]]
33 |
34 | /** Returns a function that decrypts single blocks using the key. */
35 | def decryptBlock(block: Seq[Byte]): Try[Seq[Byte]]
36 | }
37 |
38 | abstract class CanBuildBlockCipher[A <: BlockCipher[_]] {
39 |
40 | def build(params: Parameters): Try[A]
41 | }
42 |
43 | object BlockCipher {
44 |
45 | def apply[A <: BlockCipher[_]: CanBuildBlockCipher](params: Parameters)(implicit builder: CanBuildBlockCipher[A]) =
46 | builder.build(params)
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/scala/BlockCipherMode.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import scala.util.{ Try, Success, Failure }
18 |
19 | /**
20 | * Describes a block cipher mode of operation like ECB or CBC.
21 | *
22 | * Each of the methods below gets a state and returns a state. The
23 | * state is propagated like this: pre* ⇒ post* ⇒ pre*. On the first
24 | * block pre* receives the None object as the state.
25 | */
26 | trait BlockCipherMode {
27 |
28 | /** Parameters used to construct this cipher. */
29 | def params: Parameters
30 |
31 | /** Process the block before it is encrypted. */
32 | def preEncryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any])
33 |
34 | /** Process the block after it was encrypted. */
35 | def postEncryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any])
36 |
37 | /** Process the block before it is decrypted. */
38 | def preDecryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any])
39 |
40 | /** Process the block after it was decrypted. */
41 | def postDecryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any])
42 | }
43 |
44 | abstract class CanBuildBlockCipherMode[A <: BlockCipherMode] {
45 |
46 | def build(params: Parameters): Try[A]
47 | }
48 |
49 | object BlockCipherMode {
50 |
51 | def apply[A <: BlockCipherMode: CanBuildBlockCipherMode](params: Parameters)(implicit builder: CanBuildBlockCipherMode[A]) =
52 | builder.build(params)
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/scala/BlockCipherSuite.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import scala.util.{ Try, Success, Failure }
18 |
19 | /**
20 | * Represents a combination of cryptographic primitives to implement
21 | * a block cipher that can be used on arbitrary iterators.
22 | */
23 | class BlockCipherSuite[KeyType <: Key](val cipher: BlockCipher[KeyType], val mode: BlockCipherMode, val padding: BlockPadding) {
24 |
25 | private def tryIteratorToTry(it: Iterator[Try[Seq[Byte]]]) = it.foldLeft[Try[Seq[Byte]]](Success(Seq())) { (a, b) ⇒
26 | if (a.isFailure) a
27 | else if (b.isFailure) b
28 | else Success(a.get ++ b.get)
29 | }
30 |
31 | val blockSize = cipher.blockSize
32 |
33 | /**
34 | * The combined parameters of cipher, mode and padding.
35 | *
36 | * They are merged in the following order overwriting conflicting keys:
37 | * 1. padding
38 | * 2. mode
39 | * 3. cipher
40 | */
41 | lazy val params: Parameters = padding.params ++ (mode.params ++ cipher.params)
42 |
43 | def encrypt(input: Seq[Byte]): Try[Seq[Byte]] = tryIteratorToTry(encrypt(Iterator(input)))
44 |
45 | def decrypt(input: Seq[Byte]): Try[Seq[Byte]] = tryIteratorToTry(decrypt(Iterator(input)))
46 |
47 | def encrypt(input: Iterator[Seq[Byte]]): Iterator[Try[Seq[Byte]]] = new Iterator[Try[Seq[Byte]]] {
48 | // Pad the input and seperate it into blocks.
49 | val blocks = padding.pad(input, blockSize)
50 |
51 | // Saves the state between blocks.
52 | var interState: Option[Any] = None
53 |
54 | // If there was a failure.
55 | var fail = false
56 |
57 | def hasNext = blocks.hasNext && !fail
58 |
59 | def next = {
60 | // Preprocess block.
61 | val (pre, preState) = mode.preEncryptBlock(blocks.next, interState)
62 |
63 | // Encrypt block.
64 | cipher.encryptBlock(pre) match {
65 | case Failure(f) ⇒
66 | fail = true
67 | Failure(f)
68 |
69 | case Success(enc) ⇒
70 | // Postprocess block.
71 | val (post, postState) = mode.postEncryptBlock(enc, preState)
72 | // Save state and return.
73 | interState = postState
74 | Success(post)
75 | }
76 | }
77 | }
78 |
79 | def decrypt(input: Iterator[Seq[Byte]]): Iterator[Try[Seq[Byte]]] = {
80 | val decryptIterator = new Iterator[Try[Seq[Byte]]] {
81 |
82 | var fail = false
83 | var buffer = Seq[Byte]()
84 | var interState: Option[Any] = None
85 |
86 | def hasNext = (input.hasNext || buffer.length > 0) && !fail
87 |
88 | def next: Try[Seq[Byte]] = {
89 | // Fill buffer and extract single block.
90 | while (buffer.length < blockSize && input.hasNext) {
91 | buffer = buffer ++ input.next
92 | }
93 | if (buffer.length < blockSize) {
94 | fail = true
95 | return Failure(new IllegalBlockSizeException("Illegal block size encountered."))
96 | }
97 | val block = buffer.slice(0, blockSize)
98 | buffer = buffer.slice(blockSize, buffer.length)
99 |
100 | // Preprocess block.
101 | val (pre, preState) = mode.preDecryptBlock(block, interState)
102 | cipher.decryptBlock(pre) match {
103 | case Failure(f) ⇒
104 | fail = true
105 | Failure(f)
106 |
107 | case Success(dec) ⇒
108 | val (post, postState) = mode.postDecryptBlock(dec, preState)
109 | interState = postState
110 | Success(post)
111 | }
112 | }
113 | }
114 |
115 | // Since BlockPadding.unpad only accepts an Iterator[Seq[Byte]] and we have Iterator[Try[Seq[Byte]]]
116 | // we catch the failures in prepad filter and have an Iterator[Seq[Byte]]. These errors are then
117 | // given to the user by wrapping the iterator from the depad method in another iterator that
118 | // monitors these failures.
119 | var decryptionFailure: Option[Throwable] = None
120 |
121 | val prepadFilter: Iterator[Seq[Byte]] = new Iterator[Seq[Byte]] {
122 |
123 | def hasNext = decryptIterator.hasNext
124 |
125 | def next: Seq[Byte] = {
126 | decryptIterator.next match {
127 | case Failure(f) ⇒
128 | decryptionFailure = Some(f)
129 | Seq()
130 |
131 | case Success(s) ⇒
132 | s
133 | }
134 | }
135 | }
136 |
137 | val depadIterator = padding.unpad(prepadFilter, blockSize)
138 |
139 | new Iterator[Try[Seq[Byte]]] {
140 |
141 | var fail = false
142 |
143 | def hasNext = depadIterator.hasNext && !fail
144 |
145 | def next: Try[Seq[Byte]] = {
146 | val depadOutput = depadIterator.next
147 |
148 | decryptionFailure match {
149 | case Some(f) ⇒
150 | fail = true
151 | Failure(f)
152 |
153 | case _ ⇒
154 | depadOutput
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/scala/BlockPadding.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import scala.util.{ Try, Success, Failure }
18 |
19 | /** Represents the padding used to extend the data to the block size of the block cipher. */
20 | trait BlockPadding {
21 |
22 | /** Parameters used to construct this cipher. */
23 | def params: Parameters
24 |
25 | /**
26 | * Takes an iterator of byte sequences and outputs an iterator of blocks for encryption.
27 | *
28 | * Each Seq that the returned iterator returns MUST be exactly blockSize long.
29 | */
30 | def pad(input: Iterator[Seq[Byte]], blockSize: Int): Iterator[Seq[Byte]]
31 |
32 | /**
33 | * Takes an iterator of blocks and removes the padding.
34 | *
35 | * Each Seq that input contains must be exactly blockSize long.
36 | */
37 | def unpad(input: Iterator[Seq[Byte]], blockSize: Int): Iterator[Try[Seq[Byte]]]
38 | }
39 |
40 | abstract class CanBuildBlockPadding[A <: BlockPadding] {
41 |
42 | def build(params: Parameters): Try[A]
43 | }
44 |
45 | object BlockPadding {
46 |
47 | def apply[A <: BlockPadding: CanBuildBlockPadding](params: Parameters)(implicit builder: CanBuildBlockPadding[A]) = {
48 | builder.build(params)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/scala/Exceptions.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | /* Exceptions MUST never be thrown in this project. They must be
18 | * returned inside a failure.
19 | */
20 |
21 | /** Occurs when invalid padding is encountered. */
22 | class BadPaddingException(message: String) extends Exception("Bad padding: " + message)
23 |
24 | /** Occurs when a block of illegal size is given to a block cipher. */
25 | class IllegalBlockSizeException(message: String) extends Exception("Illegal block size: " + message)
26 |
27 | /** Occurs when ciphertexts are invalid. */
28 | class InvalidCiphertextException(message: String) extends Exception("Invalid ciphertext: " + message)
29 |
30 | /** Occurs when during decryption problems are encountered. */
31 | class DecryptionException(message: String) extends Exception("Decryption: " + message)
32 |
33 | /** Occurs when during encryption problems are encountered. */
34 | class EncryptionException(message: String) extends Exception("Encryption: " + message)
35 |
36 | /** Occurs when problems are encountered during key creation. */
37 | class KeyException(message: String) extends Exception("Key creation: " + message)
38 |
39 | /** Occurs when an iteratee behaves erratically. */
40 | class IterateeException(message: String) extends Exception("Iteratee: " + message)
41 |
42 | /** Occurs when a keyed hash fails. */
43 | class KeyedHashException(message: String) extends Exception("KeyedHash: " + message)
44 |
45 | /** Occurs when something is wrong with the given parameters. */
46 | class ParameterException(val symbol: Symbol, message: String) extends Exception("Parameters: " + message)
47 |
--------------------------------------------------------------------------------
/src/main/scala/Hash.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import iteratees._
18 | import scala.concurrent.{ Future, Promise }
19 |
20 | trait Hash {
21 |
22 | /** Returns an iteratee that digests its input to a hash. */
23 | def apply(): Iteratee[Seq[Byte], Seq[Byte]]
24 |
25 | /** Digests a given sequence of bytes. */
26 | def apply(data: Seq[Byte]): Seq[Byte] = apply.fold(Element(data)).run.get
27 |
28 | /** Returns a promise of the hash value and an identical iterator. */
29 | def apply(data: Iterator[Seq[Byte]]): (Iterator[Seq[Byte]], Future[Seq[Byte]]) = {
30 | val promise = Promise[Seq[Byte]]
31 | val iterator = new Iterator[Seq[Byte]] {
32 |
33 | var iteratee: Iteratee[Seq[Byte], Seq[Byte]] = apply()
34 |
35 | def hasNext: Boolean = data.hasNext
36 |
37 | def next: Seq[Byte] = {
38 | val chunk = data.next
39 | iteratee = iteratee.fold(Element(chunk))
40 | if (!data.hasNext) {
41 | val hashTry = iteratee.run
42 | promise.complete(hashTry)
43 | }
44 | chunk
45 | }
46 | }
47 | (iterator, promise.future)
48 | }
49 |
50 | /** Length of the resulting hash. */
51 | def length: Int
52 |
53 | /** Block size of the hash algorithm */
54 | def blockSize: Int
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/scala/Key.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import scala.util.{ Try, Success, Failure }
18 |
19 | /**
20 | * A wrapper for a sequence of bytes used
21 | * as a key for encryption.
22 | */
23 | trait Key extends Equals {
24 |
25 | /** Length of the key in bytes. */
26 | def length: Int
27 |
28 | /** The actual key. */
29 | def bytes: Seq[Byte]
30 |
31 | /** Inherited from Equals trait. */
32 | def canEqual(other: Any): Boolean = other match {
33 | case _: Key ⇒
34 | true
35 |
36 | case _ ⇒
37 | false
38 | }
39 |
40 | /** Equality test */
41 | override def equals(other: Any): Boolean = other match {
42 | case k: Key ⇒
43 | this.bytes == k.bytes
44 |
45 | case _ ⇒
46 | false
47 | }
48 | }
49 |
50 | /** Base trait for symmetric key builders. */
51 | trait MightBuildKey[-FromType, KeyType <: Key] {
52 |
53 | /** Tries to build the key from the given object. */
54 | def tryBuild(from: FromType): Try[KeyType]
55 | }
56 |
57 | /** Base trait for type classes generating random keys. */
58 | trait CanGenerateKey[KeyType <: Key] {
59 |
60 | /** Generate symmetric key. */
61 | def generate: KeyType
62 | }
63 |
64 | /** Singleton used to construct key objects of arbitrary length. */
65 | object Key {
66 |
67 | /** Randomly generate a symmetric key. */
68 | def generate[KeyType <: Key: CanGenerateKey]: KeyType = implicitly[CanGenerateKey[KeyType]].generate
69 | }
70 |
71 | /** Adds the toKey method to Any. */
72 | final class MightBuildKeyOp[FromType](value: FromType) {
73 |
74 | /** Tries to convert the object to a specific implementation of Key. */
75 | def toKey[KeyType <: Key]()(implicit builder: MightBuildKey[FromType, KeyType]) = {
76 | builder.tryBuild(value)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/scala/KeyedHash.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import scala.util.{ Try, Success, Failure }
18 | import iteratees._
19 | import scala.concurrent.{ Promise, Future }
20 |
21 | /** Base class for Keyed hash (Message Authentication Code) implementations. */
22 | trait KeyedHash[KeyType <: Key] {
23 |
24 | /** Returns an iteratee calculating the MAC. */
25 | def apply(key: KeyType): Try[Iteratee[Seq[Byte], Seq[Byte]]]
26 |
27 | /** Calculates the MAC. */
28 | def apply(key: KeyType, data: Seq[Byte]): Try[Seq[Byte]] = apply(key) flatMap {
29 | _.fold(Element(data)).run
30 | }
31 |
32 | /** Takes an iterator of data and returns a future containing the hash and an identical iterator */
33 | def apply(key: KeyType, data: Iterator[Seq[Byte]]): Try[(Iterator[Seq[Byte]], Future[Seq[Byte]])] = {
34 | val promise = Promise[Seq[Byte]]
35 | val iteratorTry = apply(key) map { initIteratee ⇒
36 | new Iterator[Seq[Byte]] {
37 |
38 | var iteratee = initIteratee
39 |
40 | def hasNext = data.hasNext
41 |
42 | def next = {
43 | val chunk = data.next
44 | iteratee = iteratee.fold(Element(chunk))
45 | if (!data.hasNext) {
46 | promise.complete(iteratee.run)
47 | }
48 | chunk
49 | }
50 | }
51 | }
52 | iteratorTry map { iterator ⇒
53 | (iterator, promise.future)
54 | }
55 | }
56 |
57 | def verify(key: KeyType, hash: Seq[Byte]): Try[Iteratee[Seq[Byte], Boolean]]
58 |
59 | def verify(key: KeyType, data: Seq[Byte], hash: Seq[Byte]): Try[Boolean] = verify(key, hash) flatMap {
60 | _.fold(Element(data)).run
61 | }
62 |
63 | /** The length in bytes of the MAC. */
64 | def length: Int
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/scala/Parameters.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import scala.util.{ Try, Success, Failure }
18 |
19 | object Parameters {
20 | import scala.reflect.ClassTag
21 |
22 | def apply(params: (Symbol, Any)*): Parameters = params.toMap
23 |
24 | def checkParam[A: ClassTag](params: Parameters, symbol: Symbol): Try[A] = {
25 | params.get(symbol) match {
26 | case Some(maybeParam) ⇒
27 | maybeParam match {
28 | case param: A ⇒ Success(param)
29 | case _ ⇒ Failure(new ParameterException(symbol, "Symbol " + symbol.toString + " has unexpected type: " + maybeParam.getClass.toString))
30 | }
31 | case _ ⇒ Failure(new ParameterException(symbol, "Parameter " + symbol.toString + " not found."))
32 | }
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/src/main/scala/Random.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import java.security.SecureRandom
18 |
19 | /** Secure random number generator. */
20 | object Random {
21 |
22 | /** Generates a random sequence of bytes. */
23 | def nextBytes(length: Int): Seq[Byte] = {
24 | val gen = new SecureRandom()
25 | val array = new Array[Byte](length)
26 | gen.nextBytes(array)
27 | array
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/scala/blockciphers/RSA.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.blockciphers
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | /**
21 | * BlockCipher that encrypts a byte sequence using RSA.
22 | *
23 | * Because of the internal representation of the data
24 | * leading zeroes will be lost. You should use a padding
25 | * scheme that fixes this case.
26 | */
27 | sealed trait RSA extends BlockCipher[RSAKey] {
28 |
29 | lazy val blockSize = (key.n.bitLength.toFloat / 8.0).ceil.toInt
30 |
31 | def encryptBlock(block: Seq[Byte]): Try[Seq[Byte]] = {
32 | val blocklen = block.length
33 | if (blocklen != blockSize)
34 | return Failure(new EncryptionException(s"Invalid block size. Expected length $blockSize, got $blocklen."))
35 |
36 | val m = block.os2ip
37 | if (m > key.n) return Failure(new EncryptionException("Message representation out of range."))
38 |
39 | val c = m modPow (key.e, key.n)
40 | c.i2osp(blockSize)
41 | }
42 |
43 | def decryptBlock(block: Seq[Byte]): Try[Seq[Byte]] = {
44 | if (block.length != blockSize) return Failure(new DecryptionException("Invalid block size"))
45 |
46 | val c = block.os2ip
47 | if (c > key.n) return Failure(new DecryptionException("Invalid ciphertext"))
48 |
49 | key.privateKey match {
50 | case Some(RSAPrivateCombinedKeyPart(_, p, q, dP, dQ, qInv)) ⇒
51 | val c1 = c modPow (dP, p)
52 | val c2 = c modPow (dQ, q)
53 | (((qInv * (c1 - c2)) mod p) * q + c2).i2osp(blockSize)
54 |
55 | case Some(RSAPrivatePrimeKeyPart(p, q, dP, dQ, qInv)) ⇒
56 | val c1 = c modPow (dP, p)
57 | val c2 = c modPow (dQ, q)
58 | (((qInv * (c1 - c2)) mod p) * q + c2).i2osp(blockSize)
59 |
60 | case Some(RSAPrivateExponentKeyPart(d)) ⇒
61 | val m = c modPow (d, key.n)
62 | m.i2osp(blockSize)
63 |
64 | case None ⇒
65 | Failure(new DecryptionException("No private key."))
66 | }
67 | }
68 | }
69 |
70 | object RSA {
71 |
72 | implicit val builder = new CanBuildBlockCipher[RSA] {
73 | def build(parameters: Parameters): Try[RSA] = {
74 | Parameters.checkParam[RSAKey](parameters, 'rsaKey) match {
75 | case Success(k) ⇒ Success(new RSA { val key = k; val params = parameters })
76 | case Failure(f) ⇒ Failure(f)
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/scala/blockciphers/SymmetricJavaBlockCipher.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.blockciphers
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 | import javax.crypto.Cipher
20 | import javax.crypto.spec.SecretKeySpec
21 |
22 | /** Base class for symmetric block ciphers that are implemented in the java crypto API. */
23 | sealed trait SymmetricJavaBlockCipher[KeyType <: Key] extends BlockCipher[KeyType] {
24 |
25 | protected def algo: String
26 |
27 | lazy val blockSize: Int = Cipher.getInstance(algo + "/ECB/NoPadding").getBlockSize
28 |
29 | private val secretKey: java.security.Key = new SecretKeySpec(key.bytes.toArray, "AES")
30 | private val encryptor: Cipher = Cipher.getInstance(algo + "/ECB/NoPadding")
31 | encryptor.init(Cipher.ENCRYPT_MODE, secretKey)
32 | private val decryptor: Cipher = Cipher.getInstance(algo + "/ECB/NoPadding")
33 | decryptor.init(Cipher.DECRYPT_MODE, secretKey)
34 |
35 | private def crypt(block: Seq[Byte], encrypt: Boolean): Try[Seq[Byte]] = {
36 | if (block.length == blockSize) {
37 | if (encrypt) {
38 | Success(encryptor.doFinal(block.toArray))
39 | } else {
40 | Success(decryptor.doFinal(block.toArray))
41 | }
42 | } else {
43 | Failure(
44 | new IllegalBlockSizeException("Expected block of length " + blockSize + ", got " + block.length + " bytes."))
45 | }
46 | }
47 |
48 | def encryptBlock(block: Seq[Byte]): Try[Seq[Byte]] = crypt(block, true)
49 |
50 | def decryptBlock(block: Seq[Byte]): Try[Seq[Byte]] = crypt(block, false)
51 | }
52 |
53 | sealed trait AES128 extends SymmetricJavaBlockCipher[SymmetricKey128] { lazy val algo = "AES" }
54 | sealed trait AES192 extends SymmetricJavaBlockCipher[SymmetricKey192] { lazy val algo = "AES" }
55 | sealed trait AES256 extends SymmetricJavaBlockCipher[SymmetricKey256] { lazy val algo = "AES" }
56 |
57 | object AES128 {
58 | implicit val builder = new CanBuildBlockCipher[AES128] {
59 | def build(parameters: Parameters): Try[AES128] = {
60 | Parameters.checkParam[SymmetricKey128](parameters, 'symmetricKey128) match {
61 | case Success(k) ⇒ Success(new AES128 { lazy val key = k; val params = parameters })
62 | case Failure(f) ⇒ Failure(f)
63 | }
64 | }
65 | }
66 | }
67 |
68 | object AES192 {
69 | implicit val builder = new CanBuildBlockCipher[AES192] {
70 | def build(parameters: Parameters): Try[AES192] = {
71 | Parameters.checkParam[SymmetricKey192](parameters, 'symmetricKey192) match {
72 | case Success(k) ⇒ Success(new AES192 { lazy val key = k; val params = parameters })
73 | case Failure(f) ⇒ Failure(f)
74 | }
75 | }
76 | }
77 | }
78 |
79 | object AES256 {
80 | implicit val builder = new CanBuildBlockCipher[AES256] {
81 | def build(parameters: Parameters): Try[AES256] = {
82 | Parameters.checkParam[SymmetricKey256](parameters, 'symmetricKey256) match {
83 | case Success(k) ⇒ Success(new AES256 { lazy val key = k; val params = parameters })
84 | case Failure(f) ⇒ Failure(f)
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/scala/blockciphers/Threefish.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.blockciphers
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 | import scala.annotation.tailrec
20 | import java.nio.{ ByteBuffer, ByteOrder }
21 |
22 | object Threefish {
23 |
24 | private[scalacrypt] def bytes2word(bytes: Seq[Byte]): Long =
25 | ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes.toArray).getLong(0)
26 |
27 | private[scalacrypt] def word2bytes(word: Long): Seq[Byte] =
28 | ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(word).array
29 |
30 | private[scalacrypt] def block2words(block: Seq[Byte]): Seq[Long] =
31 | for (byteWord <- block.grouped(8).toSeq) yield bytes2word(byteWord)
32 |
33 | private[scalacrypt] def words2block(words: Seq[Long]): Seq[Byte] =
34 | (for (word <- words) yield word2bytes(word)).flatten
35 |
36 | private[scalacrypt] def mix(a: Long, b: Long, r: Int): Seq[Long] = {
37 | val x = a + b
38 | val y = ((b << r) | (b >>> (64 - r))) ^ x
39 | Seq(x, y)
40 | }
41 |
42 | private[scalacrypt] def unmix(x: Long, y: Long, r: Int): Seq[Long] = {
43 | val z = y ^ x
44 | val b = ((z >>> r) | (z << (64 - r)))
45 | val a = x - b
46 | Seq(a, b)
47 | }
48 | }
49 |
50 | /** Threefish block cipher. */
51 | trait Threefish[KeyType <: Key] extends BlockCipher[KeyType] {
52 |
53 | import Threefish._
54 |
55 | /** The tweak of this block cipher. */
56 | def tweak: Seq[Byte]
57 |
58 | /** Rotational constants for the cipher. */
59 | def rotations: Seq[Seq[Int]]
60 |
61 | /** Permutation used by the cipher. */
62 | def permutation: Seq[Int]
63 |
64 | /** Reverse permutation used by the cipher. */
65 | def reversePermutation: Seq[Int]
66 |
67 | /** Number of rounds applied. */
68 | def numRounds: Int
69 |
70 | /** The number of words this cipher processes in one block. */
71 | lazy val numWords = blockSize / 8
72 |
73 | lazy val tweakWords: Seq[Long] = {
74 | val words = block2words(tweak)
75 | words :+ (words(0) ^ words(1))
76 | }
77 |
78 | lazy val keyWords: Seq[Long] = {
79 | @tailrec
80 | def xorSeq(result: Long, list: List[Long]): Long =
81 | if (list != Nil)
82 | xorSeq(result ^ list.head, list.tail)
83 | else
84 | result
85 |
86 | val words = block2words(key.bytes)
87 | words :+ xorSeq(0x1BD11BDAA9FC1A22L, words.toList)
88 | }
89 |
90 | lazy val roundKeys: Seq[Seq[Long]] = {
91 | def genRoundKey(s: Int) = {
92 | for (i <- (0 until numWords)) yield {
93 | if (i == numWords - 1)
94 | keyWords((s + i) % (numWords + 1)) + s
95 | else if (i == numWords - 2)
96 | keyWords((s + i) % (numWords + 1)) + tweakWords((s + 1) % 3)
97 | else if (i == numWords - 3)
98 | keyWords((s + i) % (numWords + 1)) + tweakWords(s % 3)
99 | else
100 | keyWords((s + i) % (numWords + 1))
101 | }
102 | }
103 |
104 | for (s <- (0 to (numRounds / 4))) yield genRoundKey(s)
105 | }
106 |
107 | def encryptBlock(block: Seq[Byte]): Try[Seq[Byte]] = {
108 | if (block.length != blockSize)
109 | return Failure(new IllegalBlockSizeException("Expected size 32, got " + block.length))
110 |
111 | val p = block2words(block)
112 |
113 | @tailrec
114 | def encryptBlockHelper(v: Seq[Long], d: Int): Seq[Long] = {
115 | if (d == numRounds) {
116 | /* Apply last round key. */
117 | (v zip roundKeys.last) map { t ⇒ t._1 + t._2 }
118 | } else {
119 | /* Add round key every 4th round */
120 | val e = if (d % 4 == 0) {
121 | val k = roundKeys(d / 4)
122 | for (i <- (0 until numWords)) yield v(i) + k(i)
123 | } else {
124 | v
125 | }
126 |
127 | /* Apply mix function */
128 | val rot = rotations(d % 8)
129 | val f = (for (i <- (0 until numWords by 2)) yield mix(e(i), e(i + 1), rot(i / 2))).flatten
130 |
131 | /* Apply permutation */
132 | val vPlus = for (i <- 0 until numWords) yield f(permutation(i))
133 |
134 | encryptBlockHelper(vPlus, d + 1)
135 | }
136 | }
137 |
138 | val c = encryptBlockHelper(p, 0)
139 | Success(words2block(c))
140 | }
141 |
142 | def decryptBlock(block: Seq[Byte]): Try[Seq[Byte]] = {
143 | if (block.length != blockSize)
144 | return Failure(new IllegalBlockSizeException("Expected size 32, got " + block.length))
145 |
146 | @tailrec
147 | def decryptBlockHelper(v: Seq[Long], d: Int): Seq[Long] = {
148 | if (d < 0) {
149 | v
150 | } else {
151 | /* Reverse permutation. */
152 | val f = for (i <- (0 until numWords)) yield v(reversePermutation(i))
153 |
154 | /* Reverse mixing. */
155 | val rot = rotations(d % 8)
156 | val e = (for (i <- (0 until numWords by 2)) yield unmix(f(i), f(i + 1), rot(i / 2))).flatten
157 |
158 | /* Substract round key every 4th round */
159 | val vMinus = if (d % 4 == 0) {
160 | val k = roundKeys(d / 4)
161 | for (i <- (0 until numWords)) yield e(i) - k(i)
162 | } else {
163 | e
164 | }
165 |
166 | decryptBlockHelper(vMinus, d - 1)
167 | }
168 | }
169 |
170 | val c = block2words(block)
171 | val v = (c zip roundKeys.last) map { t ⇒ t._1 - t._2 }
172 | val p = decryptBlockHelper(v, numRounds - 1)
173 |
174 | Success(words2block(p))
175 | }
176 | }
177 |
178 | trait Threefish256 extends Threefish[SymmetricKey256] {
179 |
180 | val blockSize = 32
181 |
182 | val numRounds = 72
183 |
184 | val rotations = Seq(
185 | Seq(14, 16),
186 | Seq(52, 57),
187 | Seq(23, 40),
188 | Seq(5, 37),
189 | Seq(25, 33),
190 | Seq(46, 12),
191 | Seq(58, 22),
192 | Seq(32, 32))
193 |
194 | val permutation: Seq[Int] = Seq(0, 3, 2, 1)
195 |
196 | val reversePermutation: Seq[Int] = Seq(0, 3, 2, 1)
197 | }
198 |
199 | trait Threefish512 extends Threefish[SymmetricKey512] {
200 |
201 | val blockSize = 64
202 |
203 | val numRounds = 72
204 |
205 | val rotations = Seq(
206 | Seq(46, 36, 19, 37),
207 | Seq(33, 27, 14, 42),
208 | Seq(17, 49, 36, 39),
209 | Seq(44, 9, 54, 56),
210 | Seq(39, 30, 34, 24),
211 | Seq(13, 50, 10, 17),
212 | Seq(25, 29, 39, 43),
213 | Seq(8, 35, 56, 22))
214 |
215 | val permutation: Seq[Int] = Seq(2, 1, 4, 7, 6, 5, 0, 3)
216 |
217 | val reversePermutation: Seq[Int] = Seq(6, 1, 0, 7, 2, 5, 4, 3)
218 | }
219 |
220 | trait Threefish1024 extends Threefish[SymmetricKey1024] {
221 |
222 | val blockSize = 128
223 |
224 | val numRounds = 80
225 |
226 | val rotations = Seq(
227 | Seq(24, 13, 8, 47, 8, 17, 22, 37),
228 | Seq(38, 19, 10, 55, 49, 18, 23, 52),
229 | Seq(33, 4, 51, 13, 34, 41, 59, 17),
230 | Seq(5, 20, 48, 41, 47, 28, 16, 25),
231 | Seq(41, 9, 37, 31, 12, 47, 44, 30),
232 | Seq(16, 34, 56, 51, 4, 53, 42, 41),
233 | Seq(31, 44, 47, 46, 19, 42, 44, 25),
234 | Seq(9, 48, 35, 52, 23, 31, 37, 20))
235 | val permutation: Seq[Int] = Seq(0, 9, 2, 13, 6, 11, 4, 15, 10, 7, 12, 3, 14, 5, 8, 1)
236 |
237 | val reversePermutation: Seq[Int] = Seq(0, 15, 2, 11, 6, 13, 4, 9, 14, 1, 8, 5, 10, 3, 12, 7)
238 | }
239 |
240 | object Threefish256 {
241 | implicit val builder = new CanBuildBlockCipher[Threefish256] {
242 | def build(parameters: Parameters): Try[Threefish256] = {
243 | Parameters.checkParam[SymmetricKey256](parameters, 'symmetricKey256) flatMap { k ⇒
244 | Parameters.checkParam[Seq[Byte]](parameters, 'tweak) flatMap { t ⇒
245 | if (t.length == 16) Success(new Threefish256 { val key = k; val tweak = t; val params = parameters })
246 | else Failure(new Exception("Invalid tweak size."))
247 | }
248 | }
249 | }
250 | }
251 | }
252 |
253 | object Threefish512 {
254 | implicit val builder = new CanBuildBlockCipher[Threefish512] {
255 | def build(parameters: Parameters): Try[Threefish512] = {
256 | Parameters.checkParam[SymmetricKey512](parameters, 'symmetricKey512) flatMap { k ⇒
257 | Parameters.checkParam[Seq[Byte]](parameters, 'tweak) flatMap { t ⇒
258 | if (t.length == 16) Success(new Threefish512 { val key = k; val tweak = t; val params = parameters })
259 | else Failure(new Exception("Invalid tweak size."))
260 | }
261 | }
262 | }
263 | }
264 | }
265 |
266 | object Threefish1024 {
267 | implicit val builder = new CanBuildBlockCipher[Threefish1024] {
268 | def build(parameters: Parameters): Try[Threefish1024] = {
269 | Parameters.checkParam[SymmetricKey1024](parameters, 'symmetricKey1024) flatMap { k ⇒
270 | Parameters.checkParam[Seq[Byte]](parameters, 'tweak) flatMap { t ⇒
271 | if (t.length == 16) Success(new Threefish1024 { val key = k; val tweak = t; val params = parameters })
272 | else Failure(new Exception("Invalid tweak size."))
273 | }
274 | }
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/main/scala/hash/MD5.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.hash
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import iteratees._
19 | import scala.util.{ Try, Success, Failure }
20 | import java.nio.{ ByteBuffer, ByteOrder }
21 | import scala.annotation.tailrec
22 |
23 | /** Secure Hash Algorithm 1 */
24 | object MD5 extends MDConstruction[(Int, Int, Int, Int)] {
25 |
26 | private def bytes2word(bytes: Seq[Byte]): Int =
27 | ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).put(bytes.toArray).getInt(0)
28 |
29 | private def word2bytes(word: Int): Seq[Byte] =
30 | ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(word).array
31 |
32 | private def rotLeft(word: Int, r: Int) = {
33 | (word << r) | (word >>> (32 - r))
34 | }
35 |
36 | private def f(t: Int, b: Int, c: Int, d: Int): Int = {
37 | if (t < 16) (b & c) | ((~b) & d)
38 | else if (t < 32) (b & d) | (c & (~d))
39 | else if (t < 48) b ^ c ^ d
40 | else c ^ (b | (~d))
41 | }
42 |
43 | private val s = Seq(
44 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
45 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
46 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
47 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21)
48 |
49 | private val k = Seq(
50 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
51 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
52 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
53 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
54 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
55 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
56 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
57 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
58 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
59 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
60 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
61 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
62 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
63 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
64 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
65 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391)
66 |
67 | private def g(t: Int): Int = {
68 | if (t < 16) t
69 | else if (t < 32) (5 * t + 1) % 16
70 | else if (t < 48) (3 * t + 5) % 16
71 | else (7 * t) % 16
72 | }
73 |
74 | def compressionFunction(state: (Int, Int, Int, Int), block: Seq[Byte]): (Int, Int, Int, Int) = {
75 | val w = block.grouped(4).toSeq map { bytes2word(_) }
76 |
77 | @tailrec
78 | def compressionHelper(state: (Int, Int, Int, Int), t: Int): (Int, Int, Int, Int) = {
79 | if (t == 64) state
80 | else state match { case (a, b, c, d) ⇒ compressionHelper((d, b + rotLeft(a + f(t, b, c, d) + k(t) + w(g(t)), s(t)), b, c), t + 1) }
81 | }
82 |
83 | val (a, b, c, d) = compressionHelper((state._1, state._2, state._3, state._4), 0)
84 | (state._1 + a, state._2 + b, state._3 + c, state._4 + d)
85 | }
86 |
87 | val initialState = (0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476)
88 |
89 | def addPadding(state: (Int, Int, Int, Int), buffer: Seq[Byte], messageLength: Long): Seq[Byte] = {
90 | val bitLength = messageLength * 8
91 | val bitLengthEncoded = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(bitLength).array.toSeq
92 | val nullPaddingLength = 64 - ((buffer.length + 1 + 8) % 64)
93 | (buffer :+ 128.toByte) ++ Seq.fill[Byte](nullPaddingLength) { 0.toByte } ++ bitLengthEncoded
94 | }
95 |
96 | def finalizeState(state: (Int, Int, Int, Int), messageLength: Long): Seq[Byte] = {
97 | word2bytes(state._1) ++ word2bytes(state._2) ++ word2bytes(state._3) ++ word2bytes(state._4)
98 | }
99 |
100 | val length = 16
101 |
102 | val blockSize = 64
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/src/main/scala/hash/MDConstruction.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.hash
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import iteratees._
19 | import scala.util.{ Try, Success, Failure }
20 | import scala.annotation.tailrec
21 |
22 | /** Template class for hash functions based on the Merkle-Dåmgard construction */
23 | abstract class MDConstruction[StateType] extends Hash {
24 |
25 | def compressionFunction(state: StateType, block: Seq[Byte]): StateType
26 |
27 | def addPadding(state: StateType, buffer: Seq[Byte], messageLength: Long): Seq[Byte]
28 |
29 | def initialState: StateType
30 |
31 | def finalizeState(state: StateType, messageLength: Long): Seq[Byte]
32 |
33 | def apply: Iteratee[Seq[Byte], Seq[Byte]] = {
34 | @tailrec
35 | def compressionHelper(state: StateType, blocks: Seq[Seq[Byte]]): StateType = {
36 | if (blocks.length == 0) {
37 | state
38 | } else {
39 | val newState = compressionFunction(state, blocks.head)
40 | compressionHelper(newState, blocks.tail)
41 | }
42 | }
43 |
44 | Iteratee.fold[Seq[Byte], (StateType, Seq[Byte], Long)]((initialState, Seq(), 0)) {
45 | case ((state, buffer, length), input) ⇒
46 | val data = buffer ++ input
47 | val newLength = length + input.length
48 | val numBlocks = data.length / this.blockSize
49 | val blocks = data.slice(0, numBlocks * this.blockSize).grouped(this.blockSize).toSeq
50 | val newBuffer = data.slice(numBlocks * this.blockSize, data.length)
51 |
52 | val newState = compressionHelper(state, blocks)
53 | Success(newState, newBuffer, newLength)
54 | } map {
55 | case (state, buffer, length) ⇒
56 | val blocks = addPadding(state, buffer, length).grouped(this.blockSize).toSeq
57 | val finalState = compressionHelper(state, blocks)
58 | finalizeState(finalState, length)
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/scala/hash/SHA1.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.hash
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import iteratees._
19 | import scala.util.{ Try, Success, Failure }
20 | import java.nio.{ ByteBuffer, ByteOrder }
21 | import scala.annotation.tailrec
22 |
23 | /** Secure Hash Algorithm 1 */
24 | object SHA1 extends MDConstruction[(Int, Int, Int, Int, Int)] {
25 |
26 | private def bytes2word(bytes: Seq[Byte]): Int =
27 | ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).put(bytes.toArray).getInt(0)
28 |
29 | private def word2bytes(word: Int): Seq[Byte] =
30 | ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(word).array
31 |
32 | private def rotLeft(word: Int, r: Int) = {
33 | (word << r) | (word >>> (32 - r))
34 | }
35 |
36 | private def f(t: Int, b: Int, c: Int, d: Int): Int = {
37 | if (t < 20) (b & c) | ((~b) & d)
38 | else if (t < 40) b ^ c ^ d
39 | else if (t < 60) (b & c) | (b & d) | (c & d)
40 | else b ^ c ^ d
41 | }
42 |
43 | private def k(t: Int): Int = {
44 | if (t < 20) 0x5A827999
45 | else if (t < 40) 0x6ED9EBA1
46 | else if (t < 60) 0x8F1BBCDC
47 | else 0xCA62C1D6
48 | }
49 |
50 | def compressionFunction(state: (Int, Int, Int, Int, Int), block: Seq[Byte]): (Int, Int, Int, Int, Int) = {
51 | val w = ((block.grouped(4).toSeq map { bytes2word(_) }) ++ Seq.fill[Int](64) { 0 }).toArray
52 | for (i <- (16 until 80)) {
53 | // This is not functional at all. It runs A LOT faster though.
54 | w(i) = rotLeft(w(i - 3) ^ w(i - 8) ^ w(i - 14) ^ w(i - 16), 1)
55 | }
56 |
57 | @tailrec
58 | def compressionHelper(state: (Int, Int, Int, Int, Int), t: Int): (Int, Int, Int, Int, Int) = {
59 | if (t == 80) state
60 | else state match {
61 | case (a, b, c, d, e) ⇒
62 | compressionHelper((rotLeft(a, 5) + f(t, b, c, d) + e + w(t) + k(t), a, rotLeft(b, 30), c, d), t + 1)
63 | }
64 | }
65 |
66 | val (a, b, c, d, e) = compressionHelper((state._1, state._2, state._3, state._4, state._5), 0)
67 | (state._1 + a, state._2 + b, state._3 + c, state._4 + d, state._5 + e)
68 | }
69 |
70 | val initialState = (0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0)
71 |
72 | def addPadding(state: (Int, Int, Int, Int, Int), buffer: Seq[Byte], messageLength: Long): Seq[Byte] = {
73 | val bitLength = messageLength * 8
74 | val bitLengthEncoded = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putLong(bitLength).array.toSeq
75 | val nullPaddingLength = 64 - ((buffer.length + 1 + 8) % 64)
76 | (buffer :+ 128.toByte) ++ Seq.fill[Byte](nullPaddingLength) { 0.toByte } ++ bitLengthEncoded
77 | }
78 |
79 | def finalizeState(state: (Int, Int, Int, Int, Int), messageLength: Long): Seq[Byte] = {
80 | word2bytes(state._1) ++ word2bytes(state._2) ++ word2bytes(state._3) ++ word2bytes(state._4) ++ word2bytes(state._5)
81 | }
82 |
83 | val length = 20
84 |
85 | val blockSize = 64
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/scala/hash/SHA256.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.hash
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import iteratees._
19 | import scala.util.{ Try, Success, Failure }
20 | import java.nio.{ ByteBuffer, ByteOrder }
21 | import scala.annotation.tailrec
22 |
23 | /** Secure Hash Algorithm 1 */
24 | object SHA256 extends MDConstruction[(Int, Int, Int, Int, Int, Int, Int, Int)] {
25 |
26 | private def bytes2word(bytes: Seq[Byte]): Int =
27 | ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).put(bytes.toArray).getInt(0)
28 |
29 | private def word2bytes(word: Int): Seq[Byte] =
30 | ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(word).array
31 |
32 | private def rotRight(word: Int, r: Int) = {
33 | (word >>> r) | (word << (32 - r))
34 | }
35 |
36 | private val k = Seq(
37 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
38 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
39 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
40 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
41 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
42 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
43 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
44 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
45 |
46 | def compressionFunction(state: (Int, Int, Int, Int, Int, Int, Int, Int), block: Seq[Byte]): (Int, Int, Int, Int, Int, Int, Int, Int) = {
47 | val w = ((block.grouped(4).toSeq map { bytes2word(_) }) ++ Seq.fill[Int](64) { 0 }).toArray
48 | for (i <- (16 until 80)) {
49 | val s0 = rotRight(w(i - 15), 7) ^ rotRight(w(i - 15), 18) ^ (w(i - 15) >>> 3)
50 | val s1 = rotRight(w(i - 2), 17) ^ rotRight(w(i - 2), 19) ^ (w(i - 2) >>> 10)
51 | w(i) = w(i - 16) + s0 + w(i - 7) + s1
52 | }
53 |
54 | @tailrec
55 | def compressionHelper(state: (Int, Int, Int, Int, Int, Int, Int, Int), t: Int): (Int, Int, Int, Int, Int, Int, Int, Int) = {
56 | if (t == 64) state
57 | else state match {
58 | case (a, b, c, d, e, f, g, h) ⇒
59 | val s1 = rotRight(e, 6) ^ rotRight(e, 11) ^ rotRight(e, 25)
60 | val ch = (e & f) ^ ((~e) & g)
61 | val temp1 = h + s1 + ch + k(t) + w(t)
62 | val s0 = rotRight(a, 2) ^ rotRight(a, 13) ^ rotRight(a, 22)
63 | val maj = (a & b) ^ (a & c) ^ (b & c)
64 | val temp2 = s0 + maj
65 | compressionHelper((temp1 + temp2, a, b, c, d + temp1, e, f, g), t + 1)
66 | }
67 | }
68 |
69 | val (a, b, c, d, e, f, g, h) = compressionHelper((state._1, state._2, state._3, state._4, state._5, state._6, state._7, state._8), 0)
70 | (state._1 + a, state._2 + b, state._3 + c, state._4 + d, state._5 + e, state._6 + f, state._7 + g, state._8 + h)
71 | }
72 |
73 | val initialState = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
74 |
75 | def addPadding(state: (Int, Int, Int, Int, Int, Int, Int, Int), buf: Seq[Byte], messageLength: Long): Seq[Byte] = {
76 | val bitLength = messageLength * 8
77 | val bitLengthEncoded = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putLong(bitLength).array.toSeq
78 | val nullPaddingLength = 64 - ((buf.length + 1 + 8) % 64)
79 | (buf :+ 128.toByte) ++ Seq.fill[Byte](nullPaddingLength) { 0.toByte } ++ bitLengthEncoded
80 | }
81 |
82 | def finalizeState(state: (Int, Int, Int, Int, Int, Int, Int, Int), messageLength: Long): Seq[Byte] = {
83 | word2bytes(state._1) ++ word2bytes(state._2) ++ word2bytes(state._3) ++ word2bytes(state._4) ++ word2bytes(state._5) ++ word2bytes(state._6) ++ word2bytes(state._7) ++ word2bytes(state._8)
84 | }
85 |
86 | val length = 32
87 |
88 | val blockSize = 64
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/scala/iteratees/Enumeratee.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.iteratees
16 |
17 | import scala.util.{ Try, Success, Failure }
18 | import xyz.wiedenhoeft.scalacrypt._
19 |
20 | trait Enumeratee[From, To] {
21 |
22 | def apply[A](inner: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]]
23 |
24 | def transform[A](inner: Iteratee[To, A]): Iteratee[From, A] = apply(inner) flatMap {
25 | _.fold(EOF).state match {
26 | case Cont(_) ⇒ Iteratee.error(new IterateeException("Iteratee must be done after EOF"))
27 | case Error(error) ⇒ Iteratee.error(error)
28 | case Done(result) ⇒ Iteratee.done(result)
29 | }
30 | }
31 | }
32 |
33 | object Enumeratee {
34 |
35 | def map[From, To](f: (From) ⇒ To) = new Enumeratee[From, To] {
36 | def apply[A](inner: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]] = inner.state match {
37 | case Cont(folder) ⇒ Iteratee.cont {
38 | case Element(element) ⇒ apply(inner.fold(Element(f(element))))
39 | case Empty ⇒ apply(inner)
40 | case EOF ⇒ Iteratee.done(inner)
41 | }
42 | case _ ⇒ Iteratee.done(inner)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/scala/iteratees/Enumerator.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.iteratees
16 |
17 | import scala.util.{ Try, Success, Failure }
18 | import xyz.wiedenhoeft.scalacrypt._
19 |
20 | trait Enumerator[E] {
21 |
22 | /** Apply enumerator to iteratee producing a new iteratee. */
23 | def apply[A](iteratee: Iteratee[E, A]): Iteratee[E, A]
24 |
25 | /**
26 | * Every element creates a new Enumerator via f. On apply
27 | * all these enumerators are applied to the iteratee.
28 | */
29 | def flatMap[B](f: (E) ⇒ Enumerator[B]): Enumerator[B] = {
30 | val base = this
31 |
32 | new Enumerator[B] {
33 | def apply[A](iteratee: Iteratee[B, A]): Iteratee[B, A] = {
34 | base(Iteratee.fold[E, Iteratee[B, A]](iteratee) { (i, e) ⇒
35 | Success(f(e).apply(i))
36 | }).run.get
37 | }
38 | }
39 | }
40 |
41 | /** Converts all Inputs of the Enumerator using f */
42 | def map[B](f: (E) ⇒ B) = flatMap { e ⇒
43 | new Enumerator[B] { def apply[A](iteratee: Iteratee[B, A]) = { iteratee.fold(Element(f(e))) } }
44 | }
45 |
46 | /** Applies the enumerator and pushes EOF. */
47 | def run[A](iteratee: Iteratee[E, A]): Try[A] = {
48 | apply(iteratee).run
49 | }
50 | }
51 |
52 | object Enumerator {
53 |
54 | /** Creates an enumerator that applies all given arguments to an iteratee. */
55 | def apply[E](elements: E*): Enumerator[E] = new Enumerator[E] {
56 |
57 | def apply[A](iteratee: Iteratee[E, A]): Iteratee[E, A] = {
58 | def applySeqToIteratee(s: Seq[E], i: Iteratee[E, A]): Iteratee[E, A] = {
59 | if (s.isEmpty) i
60 | else applySeqToIteratee(s.tail, i.fold(Element(s.head)))
61 | }
62 |
63 | applySeqToIteratee(elements, iteratee)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/scala/iteratees/Iteratee.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.iteratees
16 |
17 | import scala.util.{ Try, Success, Failure }
18 | import xyz.wiedenhoeft.scalacrypt._
19 |
20 | sealed trait State[E, A]
21 |
22 | case class Cont[E, A](folder: (Input[E]) ⇒ Iteratee[E, A]) extends State[E, A]
23 | case class Done[E, A](result: A) extends State[E, A]
24 | case class Error[E, A](error: Throwable) extends State[E, A]
25 |
26 | sealed trait Input[+E]
27 |
28 | case class Element[E](e: E) extends Input[E]
29 | object Empty extends Input[Nothing]
30 | object EOF extends Input[Nothing]
31 |
32 | /**
33 | * An immutable structure that transforms a set of data to a result.
34 | *
35 | * An iteratee is an immutable structure that can consume an input to
36 | * create a iteratee. An iteratee is only defined by its state which can
37 | * be either Cont, Error or Done. Cont holds a closure that defines the next
38 | * Iteratee depending on the next input. Done holds the result and Error holds
39 | * a Throwable.
40 | *
41 | * There are three different types of Input: Element, Empty and EOF.
42 | * The meaning of Element and Empty depends on the implementation, but
43 | * as soon as an EOF is encountered the resulting new Iteratee must be
44 | * in the Done state.
45 | */
46 | trait Iteratee[E, A] {
47 |
48 | val state: State[E, A]
49 |
50 | /** Consume an Input to Create a new Iteratee */
51 | def fold(input: Input[E]): Iteratee[E, A] = state match {
52 | case Cont(folder) ⇒ folder(input)
53 | case _ ⇒ this
54 | }
55 |
56 | /** Push an EOF and try to get the result. */
57 | def run: Try[A] = fold(EOF).state match {
58 | case Cont(_) ⇒ Failure(new IterateeException("State should be a Done after EOF."))
59 | case Done(result) ⇒ Success(result)
60 | case Error(error) ⇒ Failure(error)
61 | }
62 |
63 | /**
64 | * As soon as this iteratee finishes inputs are given to the new iteratee
65 | * defined by f eventually producing a B.
66 | */
67 | def flatMap[B](f: (A) ⇒ Iteratee[E, B]): Iteratee[E, B] = state match {
68 | case Cont(folder) ⇒ Iteratee.cont { input ⇒ folder(input).flatMap(f) }
69 | case Done(result) ⇒ f(result)
70 | case Error(error) ⇒ Iteratee.error(error)
71 | }
72 |
73 | /** Map the result using f. */
74 | def map[B](f: (A) ⇒ B): Iteratee[E, B] = flatMap[B] { result ⇒ Iteratee.done(f(result)) }
75 | }
76 |
77 | object Iteratee {
78 |
79 | /**
80 | * Create a new iteratee that uses its result type as a state that is passed to its folder.
81 | *
82 | * The resulting iteratee ignores empty inputs and results in A only after an EOF. In
83 | * combination with the map-method this iteratee is sufficient for most purposes.
84 | */
85 | def fold[E, A](initial: A)(folder: (A, E) ⇒ Try[A]): Iteratee[E, A] = Iteratee.cont {
86 | case Element(element) ⇒ folder(initial, element) match {
87 | case Success(s) ⇒ Iteratee.fold(s)(folder)
88 | case Failure(f) ⇒ Iteratee.error(f)
89 | }
90 | case Empty ⇒ Iteratee.fold(initial)(folder)
91 | case EOF ⇒ Iteratee.done(initial)
92 | }
93 |
94 | /** Returns an Iteratee in the Cont state. */
95 | def cont[E, A](folder: (Input[E]) ⇒ Iteratee[E, A]) = new Iteratee[E, A] { val state = Cont[E, A](folder) }
96 |
97 | /** Returns an iteratee that is already in the Done state with the given result. */
98 | def done[E, A](result: A) = new Iteratee[E, A] { val state = Done[E, A](result) }
99 |
100 | /** Returns an iteratee that is in the Error state. */
101 | def error[E, A](error: Throwable) = new Iteratee[E, A] { val state = Error[E, A](error) }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/scala/keys/RSAKey.scala:
--------------------------------------------------------------------------------
1 | package xyz.wiedenhoeft.scalacrypt
2 |
3 | import scala.util.{ Try, Success, Failure }
4 |
5 | sealed trait RSAPrivateKeyPart
6 |
7 | /** A private key that only holds the private exponent. */
8 | final case class RSAPrivateExponentKeyPart(d: BigInt) extends RSAPrivateKeyPart
9 |
10 | /** A private key part that holds parameters for faster application of the private key. */
11 | final case class RSAPrivatePrimeKeyPart(p: BigInt, q: BigInt, dP: BigInt, dQ: BigInt, qInv: BigInt) extends RSAPrivateKeyPart
12 |
13 | /** A private key part that holds parameters for faster application of the private key. */
14 | final case class RSAPrivateCombinedKeyPart(d: BigInt, p: BigInt, q: BigInt, dP: BigInt, dQ: BigInt, qInv: BigInt) extends RSAPrivateKeyPart
15 |
16 | /** Asymmetric RSA key. */
17 | sealed abstract class RSAKey extends Key {
18 |
19 | /** RSA modulus. */
20 | val n: BigInt
21 |
22 | /** Public exponent. */
23 | val e: BigInt
24 |
25 | /** The private part of the key */
26 | val privateKey: Option[RSAPrivateKeyPart]
27 |
28 | /** Whether this key should be kept secret. */
29 | def isPrivateKey: Boolean = privateKey.isDefined
30 |
31 | /** Whether it is safe to publish this key. */
32 | def isPublicKey: Boolean = !isPrivateKey
33 |
34 | /** Returns a RSA key that contains only the public parts. */
35 | def publicKey: RSAKey = {
36 | val base = this
37 | new RSAKey {
38 | val n = base.n
39 | val e = base.e
40 | val privateKey = None
41 | }
42 | }
43 |
44 | def length = (n.bitLength.toFloat / 8.0).ceil.toInt
45 |
46 | def bytes: Seq[Byte] = {
47 | def f(identifier: Int, value: BigInt): List[Byte] = {
48 | val byteArray: List[Byte] = value.toByteArray.toList
49 | val lengthBytes: List[Byte] = java.nio.ByteBuffer.allocate(4).putInt(byteArray.length).array.toList
50 | identifier.toByte :: lengthBytes ::: byteArray ::: Nil
51 | }
52 |
53 | f(0, n) ::: f(1, e) :::
54 | (privateKey match {
55 | case Some(RSAPrivateExponentKeyPart(d)) ⇒
56 | f(2, d)
57 | case Some(RSAPrivatePrimeKeyPart(p, q, dP, dQ, qInv)) ⇒
58 | f(3, p) ::: f(4, q) ::: f(5, dP) ::: f(6, dQ) ::: f(7, qInv) ::: Nil
59 | case Some(RSAPrivateCombinedKeyPart(d, p, q, dP, dQ, qInv)) ⇒
60 | f(2, d) ::: f(3, p) ::: f(4, q) ::: f(5, dP) ::: f(6, dQ) ::: f(7, qInv) ::: Nil
61 | case None ⇒
62 | Nil
63 | }) ::: Nil
64 | }
65 | }
66 |
67 | object RSAKey {
68 |
69 | implicit val mightBuildPublicFromTuple = new MightBuildKey[(Seq[Byte], Seq[Byte]), RSAKey] {
70 | def tryBuild(keyTuple: (Seq[Byte], Seq[Byte])): Try[RSAKey] = Success(new RSAKey {
71 | val n = keyTuple._2.os2ip
72 | val e = keyTuple._1.os2ip
73 | val privateKey = None
74 | })
75 | }
76 |
77 | implicit val mightBuildPrivateFromTuple = new MightBuildKey[(Seq[Byte], Seq[Byte], Seq[Byte]), RSAKey] {
78 | def tryBuild(keyTuple: (Seq[Byte], Seq[Byte], Seq[Byte])): Try[RSAKey] = Success(new RSAKey {
79 | val n = keyTuple._3.os2ip
80 | val e = keyTuple._1.os2ip
81 | val privateKey = Some(new RSAPrivateExponentKeyPart(keyTuple._2.os2ip))
82 | })
83 | }
84 |
85 | implicit val mightBuildFromBytes = new MightBuildKey[Seq[Byte], RSAKey] {
86 |
87 | def tryBuild(keyBytes: Seq[Byte]): Try[RSAKey] = {
88 | def createMap(map: Map[Int, BigInt], bytes: Seq[Byte]): Try[Map[Int, BigInt]] = {
89 | if (bytes.length == 0) {
90 | Success(Map[Int, BigInt]())
91 | } else if (bytes.length < 5) {
92 | Failure(new KeyException("Invalid length of RSA key."))
93 | } else {
94 | val identifier = bytes(0)
95 | val length = java.nio.ByteBuffer.allocate(4).put(bytes.slice(1, 5).toArray).getInt(0)
96 | val withoutHeader = bytes.slice(5, bytes.length)
97 | if (withoutHeader.length < length) {
98 | Failure(new KeyException("Invalid length of RSA key."))
99 | } else {
100 | val data = withoutHeader.slice(0, length)
101 | val newMap = map + ((identifier.toInt, BigInt(data.toArray)))
102 | createMap(newMap, withoutHeader.slice(length, withoutHeader.length)) match {
103 | case Success(rMap) ⇒
104 | Success(newMap ++ rMap)
105 |
106 | case f: Failure[_] ⇒
107 | f
108 | }
109 | }
110 | }
111 | }
112 |
113 | val map: Map[Int, BigInt] = createMap(Map[Int, BigInt](), keyBytes) match {
114 | case Success(m) ⇒
115 | m
116 |
117 | case f: Failure[_] ⇒
118 | return f.asInstanceOf[Try[RSAKey]]
119 | }
120 |
121 | val key: Option[RSAPrivateKeyPart] =
122 | if ((2 to 7).map({ map.contains(_) }).filter({ !_ }).length == 0)
123 | Some(new RSAPrivateCombinedKeyPart(map(2), map(3), map(4), map(5), map(6), map(7)))
124 | else if ((3 to 7).map({ map.contains(_) }).filter({ !_ }).length == 0)
125 | Some(new RSAPrivatePrimeKeyPart(map(3), map(4), map(5), map(6), map(7)))
126 | else if (map.contains(2))
127 | Some(new RSAPrivateExponentKeyPart(map(2)))
128 | else
129 | None
130 |
131 | if (!map.contains(0) || !map.contains(1)) {
132 | Failure(new KeyException("Important parameters missing in RSAKey."))
133 | } else Success(new RSAKey {
134 | val n = map.get(0).get
135 | val e = map.get(1).get
136 | val privateKey = key
137 | })
138 | }
139 | }
140 |
141 | implicit val canGenerateKey = new CanGenerateKey[RSAKey] {
142 | def generate = new RSAKey {
143 | val random = new scala.util.Random(new java.security.SecureRandom)
144 |
145 | val p = BigInt.probablePrime(2048, random)
146 | val q = BigInt.probablePrime(2048, random)
147 |
148 | // Public Key
149 | val n = p * q
150 | val e = BigInt(65537)
151 |
152 | // Private key variant 1
153 | val ϕ = (p - 1) * (q - 1)
154 | val d = e modInverse ϕ
155 | val dP = d mod (p - 1)
156 | val dQ = d mod (q - 1)
157 | val qInv = q modInverse p
158 | val privateKey = Some(new RSAPrivateCombinedKeyPart(d, p, q, dP, dQ, qInv))
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/main/scala/keys/SymmetricKey.scala:
--------------------------------------------------------------------------------
1 | package xyz.wiedenhoeft.scalacrypt
2 |
3 | import scala.util.{ Try, Success, Failure }
4 |
5 | /** A 128 bit symmetric key. */
6 | sealed abstract class SymmetricKey128 extends Key
7 |
8 | object SymmetricKey128 {
9 |
10 | implicit val mightBuildKey = new MightBuildKey[Seq[Byte], SymmetricKey128] {
11 |
12 | def tryBuild(keyBytes: Seq[Byte]): Try[SymmetricKey128] = {
13 | if (keyBytes.length == 128 / 8) {
14 | Success(new SymmetricKey128Impl(keyBytes))
15 | } else {
16 | Failure(new KeyException("Illegal key size. SymmetricKey128 must be 128 bits long."))
17 | }
18 | }
19 |
20 | private class SymmetricKey128Impl(val bytes: Seq[Byte]) extends SymmetricKey128 {
21 |
22 | def length: Int = 16
23 | }
24 | }
25 |
26 | implicit val canGenerateKey = new CanGenerateKey[SymmetricKey128] {
27 | def generate = Random.nextBytes(16).toKey[SymmetricKey128].get
28 | }
29 | }
30 |
31 | /** A 192 bit symmetric key. */
32 | sealed abstract class SymmetricKey192 extends Key
33 |
34 | object SymmetricKey192 {
35 |
36 | implicit val mightBuildKey = new MightBuildKey[Seq[Byte], SymmetricKey192] {
37 |
38 | def tryBuild(keyBytes: Seq[Byte]): Try[SymmetricKey192] = {
39 | if (keyBytes.length == 192 / 8) {
40 | Success(new SymmetricKey192Impl(keyBytes))
41 | } else {
42 | Failure(new KeyException("Illegal key size. SymmetricKey192 must be 192 bits long."))
43 | }
44 | }
45 |
46 | private class SymmetricKey192Impl(val bytes: Seq[Byte]) extends SymmetricKey192 {
47 |
48 | def length: Int = 24
49 | }
50 | }
51 |
52 | implicit val canGenerateKey = new CanGenerateKey[SymmetricKey192] {
53 | def generate = Random.nextBytes(24).toKey[SymmetricKey192].get
54 | }
55 | }
56 |
57 | /** A 256 bit symmetric key. */
58 | sealed abstract class SymmetricKey256 extends Key
59 |
60 | object SymmetricKey256 {
61 |
62 | implicit val mightBuildKey = new MightBuildKey[Seq[Byte], SymmetricKey256] {
63 |
64 | def tryBuild(keyBytes: Seq[Byte]): Try[SymmetricKey256] = {
65 | if (keyBytes.length == 256 / 8) {
66 | Success(new SymmetricKey256Impl(keyBytes))
67 | } else {
68 | Failure(new KeyException("Illegal key size. SymmetricKey256 must be 256 bits long."))
69 | }
70 | }
71 |
72 | private class SymmetricKey256Impl(val bytes: Seq[Byte]) extends SymmetricKey256 {
73 |
74 | def length: Int = 32
75 | }
76 | }
77 |
78 | implicit val canGenerateKey = new CanGenerateKey[SymmetricKey256] {
79 | def generate = Random.nextBytes(32).toKey[SymmetricKey256].get
80 | }
81 | }
82 |
83 | /** A 512 bit symmetric key. */
84 | sealed abstract class SymmetricKey512 extends Key
85 |
86 | object SymmetricKey512 {
87 |
88 | implicit val mightBuildKey = new MightBuildKey[Seq[Byte], SymmetricKey512] {
89 |
90 | def tryBuild(keyBytes: Seq[Byte]): Try[SymmetricKey512] = {
91 | if (keyBytes.length == 512 / 8) {
92 | Success(new SymmetricKey512Impl(keyBytes))
93 | } else {
94 | Failure(new KeyException("Illegal key size. SymmetricKey512 must be 512 bits long."))
95 | }
96 | }
97 |
98 | private class SymmetricKey512Impl(val bytes: Seq[Byte]) extends SymmetricKey512 {
99 |
100 | def length: Int = 64
101 | }
102 | }
103 |
104 | implicit val canGenerateKey = new CanGenerateKey[SymmetricKey512] {
105 | def generate = Random.nextBytes(64).toKey[SymmetricKey512].get
106 | }
107 | }
108 |
109 | /** A 1024 bit symmetric key. */
110 | sealed abstract class SymmetricKey1024 extends Key
111 |
112 | object SymmetricKey1024 {
113 |
114 | implicit val mightBuildKey = new MightBuildKey[Seq[Byte], SymmetricKey1024] {
115 |
116 | def tryBuild(keyBytes: Seq[Byte]): Try[SymmetricKey1024] = {
117 | if (keyBytes.length == 1024 / 8) {
118 | Success(new SymmetricKey1024Impl(keyBytes))
119 | } else {
120 | Failure(new KeyException("Illegal key size. SymmetricKey1024 must be 1024 bits long."))
121 | }
122 | }
123 |
124 | private class SymmetricKey1024Impl(val bytes: Seq[Byte]) extends SymmetricKey1024 {
125 |
126 | def length: Int = 128
127 | }
128 | }
129 |
130 | implicit val canGenerateKey = new CanGenerateKey[SymmetricKey1024] {
131 | def generate = Random.nextBytes(128).toKey[SymmetricKey1024].get
132 | }
133 | }
134 |
135 | /** A symmetric key of arbitrary length. */
136 | sealed abstract class SymmetricKeyArbitrary extends Key
137 |
138 | object SymmetricKeyArbitrary {
139 |
140 | implicit val MightBuildKey = new MightBuildKey[Seq[Byte], SymmetricKeyArbitrary] {
141 |
142 | def tryBuild(keyBytes: Seq[Byte]): Try[SymmetricKeyArbitrary] = {
143 | Success(new SymmetricKeyArbitraryImpl(keyBytes))
144 | }
145 |
146 | private class SymmetricKeyArbitraryImpl(val bytes: Seq[Byte]) extends SymmetricKeyArbitrary {
147 |
148 | def length: Int = bytes.length
149 | }
150 | }
151 |
152 | implicit val canGenerateKey = new CanGenerateKey[SymmetricKeyArbitrary] {
153 | def generate = Random.nextBytes(32).toKey[SymmetricKeyArbitrary].get
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/main/scala/khash/Hmac.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.khash
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import iteratees._
19 | import scala.util.{ Try, Success, Failure }
20 |
21 | class Hmac(hash: Hash) extends KeyedHash[Key] {
22 |
23 | def apply(key: Key): Try[Iteratee[Seq[Byte], Seq[Byte]]] = {
24 | val key1 = if (key.length > hash.blockSize) {
25 | hash(key.bytes)
26 | } else {
27 | key.bytes
28 | }
29 |
30 | val key2 = key1 ++ Seq.fill[Byte](hash.blockSize - key1.length) { 0.toByte }
31 |
32 | val oKeyPad = Seq.fill[Byte](hash.blockSize)(0x5c.toByte) xor key2
33 | val iKeyPad = Seq.fill[Byte](hash.blockSize)(0x36.toByte) xor key2
34 |
35 | Success(
36 | hash.apply.fold(Element(iKeyPad)) map { innerHash ⇒
37 | hash(oKeyPad ++ innerHash)
38 | })
39 | }
40 |
41 | def verify(key: Key, hash: Seq[Byte]): Try[Iteratee[Seq[Byte], Boolean]] = apply(key) map { _ map { _ == hash } }
42 |
43 | val length = hash.length
44 | }
45 |
46 | import hash._
47 |
48 | /** HMAC-SHA1 implementation of Mac. */
49 | object HmacSHA1 extends Hmac(SHA1)
50 |
51 | /** HMAC-SHA256 implementation of Mac. */
52 | object HmacSHA256 extends Hmac(SHA256)
53 |
--------------------------------------------------------------------------------
/src/main/scala/khash/PBKDF2.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.khash
16 |
17 | import scala.util.{ Try, Success, Failure }
18 | import xyz.wiedenhoeft.scalacrypt._
19 | import scala.annotation.tailrec
20 | import iteratees._
21 |
22 | /* Factory for PBKDF2 KeyedHash instances. */
23 | object PBKDF2 {
24 |
25 | /* Creates a Keyed hash that implements PBKDF2. The salt is passed in as the data. */
26 | def apply(algorithm: KeyedHash[Key], iterations: Int, len: Int): KeyedHash[Key] = new KeyedHash[Key] {
27 |
28 | def length = len
29 |
30 | def apply(key: Key): Try[Iteratee[Seq[Byte], Seq[Byte]]] = {
31 | algorithm(key) map { initialIteratee ⇒
32 | Iteratee.fold(initialIteratee) { (iteratee: Iteratee[Seq[Byte], Seq[Byte]], chunk: Seq[Byte]) ⇒
33 | Success(iteratee.fold(Element(chunk)))
34 | } flatMap { keyedHash ⇒
35 | val numBlocks = (length.toFloat / algorithm.length).ceil.toInt
36 |
37 | /* Calculates a block */
38 | def calcBlock(blockNum: Int): Try[Seq[Byte]] = {
39 | val blockNumBytes = java.nio.ByteBuffer.allocate(4).putInt(blockNum).array
40 | val initial: Seq[Byte] = keyedHash.fold(Element(blockNumBytes)).run.get
41 |
42 | @tailrec
43 | def calcBlockHelper(block: Seq[Byte], previousU: Seq[Byte], iteration: Int): Try[Seq[Byte]] = {
44 | if (iteration > iterations) {
45 | Success(block)
46 | } else {
47 | val uTry = algorithm(key, previousU)
48 | if (uTry.isSuccess) {
49 | val u = uTry.get
50 | calcBlockHelper(block xor u, u, iteration + 1)
51 | } else {
52 | Failure(uTry.failed.get)
53 | }
54 | }
55 | }
56 |
57 | calcBlockHelper(initial, initial, 2)
58 | }
59 |
60 | val blocks = for (blockNum <- 1 to numBlocks) yield calcBlock(blockNum)
61 | val failures = blocks filter { _.isFailure }
62 | if (failures.length == 0) Iteratee.done((blocks map { _.get }).flatten.slice(0, length))
63 | else Iteratee.error(failures(0).failed.get)
64 | }
65 | }
66 | }
67 |
68 | def verify(key: Key, hash: Seq[Byte]): Try[Iteratee[Seq[Byte], Boolean]] = apply(key) map { _ map { _ == hash } }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/scala/khash/RSASSA_PSS.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.khash
16 |
17 | import scala.util.{ Try, Success, Failure }
18 | import xyz.wiedenhoeft.scalacrypt._
19 | import iteratees._
20 | import blockciphers.RSA
21 |
22 | object RSASSA_PSS {
23 |
24 | def apply(hashFunction: Hash = hash.SHA256, saltLength: Int = 32, saltGenerator: (Int) ⇒ Seq[Byte] = Random.nextBytes _) = new KeyedHash[RSAKey] {
25 |
26 | private def mgf1(seed: Seq[Byte], length: Int) = {
27 | val numBlocks = (length.toFloat / hashFunction.length.toFloat).ceil.toInt
28 | (for (i <- (0 until numBlocks)) yield hashFunction(seed ++ BigInt(i).i2osp(4).get)).flatten.slice(0, length)
29 | }
30 |
31 | def apply(key: RSAKey): Try[Iteratee[Seq[Byte], Seq[Byte]]] = {
32 | if (!key.isPrivateKey) return Failure(new KeyedHashException("No private key."))
33 | Success(hashFunction.apply flatMap { mHash ⇒
34 | val emLen = key.length
35 | val emBits = key.n.bitLength - 1
36 | val hLen = hashFunction.length
37 | val sLen = saltLength
38 | val dbLen = emLen - hLen - 1
39 | val psLen = dbLen - sLen - 1
40 |
41 | val salt = saltGenerator(saltLength)
42 | val mTick = Seq.fill[Byte](8) { 0.toByte } ++ mHash ++ salt
43 | val h = hashFunction(mTick)
44 |
45 | val ps = Seq.fill[Byte](psLen) { 0.toByte }
46 | val db = (ps :+ 1.toByte) ++ salt
47 | val dbMask = mgf1(h, dbLen)
48 |
49 | val wipeBits = 8 * emLen - emBits
50 | val saveBits = 8 - wipeBits
51 | var wipeMask = 0.toByte
52 | for (i <- (0 until saveBits)) {
53 | wipeMask = (wipeMask | (1.toByte << i).toByte).toByte
54 | }
55 |
56 | val maskedDB = (db xor dbMask).toArray
57 | maskedDB(0) = (maskedDB(0) & wipeMask).toByte
58 |
59 | val em = (maskedDB ++ h) :+ 0xbc.toByte
60 |
61 | val cipher = BlockCipher[RSA](Parameters('rsaKey -> key)).get
62 | cipher.decryptBlock(em) match {
63 | case Success(s) ⇒ Iteratee.done(s)
64 | case Failure(f) ⇒ Iteratee.error(f)
65 | }
66 | })
67 | }
68 |
69 | def verify(key: RSAKey, hash: Seq[Byte]): Try[Iteratee[Seq[Byte], Boolean]] = {
70 | val cipher = BlockCipher[RSA](Parameters('rsaKey -> key)).get
71 | val em = cipher.encryptBlock(hash) match {
72 | case Success(s) ⇒ s
73 | case Failure(f) ⇒ return Failure(f)
74 | }
75 |
76 | val emLen = key.length
77 | val emBits = key.n.bitLength - 1
78 | val hLen = hashFunction.length
79 | val sLen = saltLength
80 | val dbLen = emLen - hLen - 1
81 | val psLen = dbLen - sLen - 1
82 | val standardError = new KeyedHashException("Inconsistent")
83 |
84 | if (em.length != emLen || em(emLen - 1) != 0xbc.toByte) return Failure(standardError)
85 | val maskedDB = em.slice(0, dbLen)
86 | val h = em.slice(dbLen, dbLen + hLen)
87 | val dbMask = mgf1(h, dbLen)
88 |
89 | val wipeBits = 8 * emLen - emBits
90 | val saveBits = 8 - wipeBits
91 | var wipeMask = 0.toByte
92 | for (i <- (0 until saveBits)) {
93 | wipeMask = (wipeMask | (1.toByte << i).toByte).toByte
94 | }
95 |
96 | val db = (dbMask xor maskedDB).toArray
97 | db(0) = (db(0) & wipeMask).toByte
98 |
99 | val ps = Seq.fill[Byte](psLen) { 0.toByte }
100 | if (db.slice(0, psLen).toSeq != ps || db(psLen) != 1.toByte) return Failure(standardError)
101 |
102 | val salt = db.slice(psLen + 1, dbLen).toSeq
103 | Success(hashFunction.apply map { mHash ⇒
104 | val mTick = Seq.fill[Byte](8) { 0.toByte } ++ mHash ++ salt
105 | h == hashFunction(mTick)
106 | })
107 | }
108 |
109 | def length = 0
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/scala/modes/CBC.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.modes
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | sealed trait CBC extends BlockCipherMode {
21 |
22 | import scala.language.implicitConversions
23 |
24 | def iv: Seq[Byte]
25 |
26 | def preEncryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
27 | val prev: Seq[Byte] = state match {
28 | // Use the previous ciphertext block.
29 | case Some(s) ⇒
30 | s.asInstanceOf[Seq[Byte]]
31 |
32 | // First block. Use the IV.
33 | case _ ⇒
34 | iv
35 | }
36 |
37 | // Xor the previous ciphertext block/IV to the cleartext block.
38 | (block xor prev, None)
39 | }
40 |
41 | def postEncryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
42 | // Save the ciphertext for use in preEncryptBlock.
43 | (block, Some(block))
44 | }
45 |
46 | def preDecryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
47 | state match {
48 | // Supply previous ciphertext for xoring and save ciphertext.
49 | case Some(s) ⇒
50 | (block, Some((s.asInstanceOf[Seq[Byte]], block)))
51 |
52 | // First Block. Supply IV for xoring and save ciphertext.
53 | case _ ⇒
54 | (block, Some((iv, block)))
55 | }
56 | }
57 |
58 | def postDecryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
59 | // Tuple contains previous and current ciphertext block.
60 | val tuple = state.get.asInstanceOf[(Seq[Byte], Seq[Byte])]
61 |
62 | // Xor decrypted block with previous ciphertext block. Set state to current ciphertext.
63 | (block xor tuple._1, Some(tuple._2))
64 | }
65 | }
66 |
67 | object CBC {
68 |
69 | implicit val builder = new CanBuildBlockCipherMode[CBC] {
70 | def build(parameters: Parameters) = {
71 | Parameters.checkParam[Seq[Byte]](parameters, 'iv) match {
72 | case Success(s) ⇒ Success(new CBC { val iv = s; val params = parameters })
73 | case Failure(f) ⇒ Failure(f)
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/scala/modes/ECB.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.modes
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | sealed trait ECB extends BlockCipherMode {
21 |
22 | def preEncryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
23 | (block, None)
24 | }
25 |
26 | def postEncryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
27 | (block, None)
28 | }
29 |
30 | def preDecryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
31 | (block, None)
32 | }
33 |
34 | def postDecryptBlock(block: Seq[Byte], state: Option[Any]): (Seq[Byte], Option[Any]) = {
35 | (block, None)
36 | }
37 | }
38 |
39 | object ECB {
40 |
41 | implicit val builder = new CanBuildBlockCipherMode[ECB] {
42 | def build(parameters: Parameters) = Success(new ECB { val params = parameters })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/scala/package.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import java.util.Base64
18 |
19 | import scala.util.{ Try, Success, Failure }
20 |
21 | object `package` {
22 |
23 | import scala.language.implicitConversions
24 |
25 | /** Implicit Conversion that adds the toKey method to every class. */
26 | implicit def toCanBuildKeyOp[FromType](from: FromType) = {
27 | new MightBuildKeyOp[FromType](from)
28 | }
29 |
30 | implicit def toRichByteSeq(value: Seq[Byte]): RichByteSeq = new RichByteSeq(value)
31 | /** Adds methods to Seq[Byte]. */
32 | class RichByteSeq(value: Seq[Byte]) {
33 |
34 | def toBase64String: String = Base64.getEncoder.encodeToString(value.toArray)
35 |
36 | def xor(other: Seq[Byte]): Seq[Byte] = {
37 | def min(a: Int, b: Int): Int = if (a < b) a else b
38 | for (i <- (0 until min(value.length, other.length))) yield (value(i) ^ other(i)).toByte
39 | }
40 |
41 | def os2ip: BigInt = {
42 | def byteToInt(byte: Byte): Int = {
43 | val result = byte.toInt
44 | if (result < 0) result + 256
45 | else result
46 | }
47 |
48 | var result = BigInt(0)
49 | for (i <- (0 until value.length)) {
50 | val exponent = value.length - 1 - i
51 | result += (BigInt(256) pow exponent) * byteToInt(value(i))
52 | }
53 | result
54 | }
55 | }
56 |
57 | implicit def toRichString(value: String): RichString = new RichString(value)
58 | /** Adds methods to String. */
59 | class RichString(value: String) {
60 |
61 | def toBase64Bytes: Seq[Byte] = Base64.getDecoder.decode(value.filter(!_.isWhitespace))
62 | }
63 |
64 | implicit def toRichBigInt(value: BigInt): RichBigInt = new RichBigInt(value)
65 | /** Adds methods to BigInt. */
66 | class RichBigInt(value: BigInt) {
67 |
68 | /** I2OSP as defined by PKCS#1 v2.1 */
69 | def i2osp(length: Int): Try[Seq[Byte]] = {
70 | val base = BigInt(256)
71 | var exponent = length
72 |
73 | if (length <= 0) return Failure(new Exception("Invalid length"))
74 | if (value < 0) return Failure(new Exception("Negative values can not be converted using I2OSP"))
75 | val maxValue = (base pow exponent) - 1
76 | if (value > maxValue) return Failure(new Exception(s"Value too large: $value (max. $maxValue when length is $length)"))
77 |
78 | var remaining = value
79 | val result = new Array[Byte](length)
80 |
81 | /* Calculate the digits (base 256, big endian). */
82 | while (exponent > 0) {
83 | exponent -= 1
84 |
85 | val factor = base pow exponent
86 | val remainingBackup = remaining
87 | remaining = remaining mod factor
88 | val difference = remainingBackup - remaining
89 | val index = (length - 1) - exponent
90 | result(index) = (difference / factor).toByte
91 | }
92 |
93 | Success(result)
94 | }
95 | }
96 |
97 | type Parameters = Map[Symbol, Any]
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/scala/paddings/NoPadding.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.paddings
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | sealed trait NoPadding extends BlockPadding {
21 |
22 | def pad(input: Iterator[Seq[Byte]], blockSize: Int): Iterator[Seq[Byte]] = new Iterator[Seq[Byte]] {
23 |
24 | var buffer = Seq[Byte]()
25 |
26 | def hasNext = input.hasNext || buffer.length > 0
27 |
28 | def next = {
29 | while (buffer.length < blockSize && input.hasNext) {
30 | buffer = buffer ++ input.next
31 | }
32 | if (buffer.length < blockSize) {
33 | //Most likely yields an error in the block cipher
34 | buffer
35 | } else {
36 | val rv = buffer.slice(0, blockSize)
37 | buffer = buffer.slice(blockSize, buffer.length)
38 | rv
39 | }
40 | }
41 | }
42 |
43 | def unpad(input: Iterator[Seq[Byte]], blockSize: Int): Iterator[Try[Seq[Byte]]] = input map { Success(_) }
44 | }
45 |
46 | object NoPadding {
47 |
48 | implicit val builder = new CanBuildBlockPadding[NoPadding] {
49 | def build(parameters: Parameters) = Success(new NoPadding { val params = parameters })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/scala/paddings/OAEP.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.paddings
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | /**
21 | * Optimal asymmetric encryption padding as defined in PKCS#1 v2.1
22 | *
23 | * Block (length k)
24 | * 0 1 2 3 4 ... hLen hlen+1 ... k
25 | * -----------------------------
26 | * 0x00 ------Seed------ ------DB----
27 | *
28 | * k=16
29 | * hlen=5
30 | * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
31 | * 0 s s s s s d d d d d d d d d d
32 | *
33 | * DB (length k - hLen - 1)
34 | * 0 1 2 ... hlen-1 hlen ... k-hLen-1
35 | * ----------------------------------
36 | * ------lHash----- ------mPart------
37 | *
38 | * k=16
39 | * hlen=5
40 | * 0 1 2 3 4 5 6 7 8 9 10
41 | * h h h h h m m m m m m
42 | */
43 |
44 | sealed trait OAEP extends BlockPadding {
45 |
46 | /** Hash function. */
47 | def hashFunction: Hash
48 |
49 | private def mgf(seed: Seq[Byte], length: Int) = {
50 | val numBlocks = (length.toFloat / hashFunction.length.toFloat).ceil.toInt
51 | (for (i <- (0 until numBlocks)) yield hashFunction(seed ++ BigInt(i).i2osp(4).get)).flatten.slice(0, length)
52 | }
53 |
54 | /** Optional label that can be verified during decryption */
55 | def label: Seq[Byte] = Seq[Byte]()
56 |
57 | /** Function that generates a seed of the given length. */
58 | def seedGenerator: (Int) ⇒ Seq[Byte]
59 |
60 | /** Hash of the label. */
61 | lazy val labelHash = hashFunction(label)
62 |
63 | def pad(data: Iterator[Seq[Byte]], blockSize: Int): Iterator[Seq[Byte]] = new Iterator[Seq[Byte]] {
64 | var buffer = Seq[Byte]()
65 | val maxMessageLength = blockSize - 2 * hashFunction.length - 2
66 |
67 | def hasNext = data.hasNext || buffer.length > 0
68 |
69 | def next: Seq[Byte] = {
70 | while (buffer.length < maxMessageLength && data.hasNext) buffer = buffer ++ data.next
71 |
72 | val message = if (buffer.length > maxMessageLength) {
73 | val rv = buffer
74 | buffer = buffer.slice(maxMessageLength, buffer.length)
75 | rv
76 | } else {
77 | val rv = buffer
78 | buffer = Seq[Byte]()
79 | rv
80 | }
81 |
82 | val seed = seedGenerator(hashFunction.length)
83 | val db = (labelHash ++ (Seq.fill[Byte](maxMessageLength - message.length) { 0.toByte }) :+ 1.toByte) ++ message
84 |
85 | val dbMask = mgf(seed, db.length)
86 | val maskedDB = db xor dbMask
87 |
88 | val seedMask = mgf(maskedDB, seed.length)
89 | val maskedSeed = seed xor seedMask
90 |
91 | (0.toByte +: maskedSeed) ++ maskedDB
92 | }
93 | }
94 |
95 | def unpad(data: Iterator[Seq[Byte]], blockSize: Int): Iterator[Try[Seq[Byte]]] = new Iterator[Try[Seq[Byte]]] {
96 |
97 | def hasNext = data.hasNext
98 |
99 | def next: Try[Seq[Byte]] = {
100 | val block = data.next
101 | if (block.length != blockSize)
102 | return Failure(new IllegalBlockSizeException("Unpad needs blocks of correct length."))
103 |
104 | /* Never specify what went wrong exactly. */
105 | val standardError = Some(new BadPaddingException("Invalid OAEP"))
106 | var error: Option[Exception] = None
107 |
108 | if (block(0) != 0.toByte) {
109 | error = standardError
110 | }
111 | val maskedSeed = block.slice(1, hashFunction.length + 1)
112 | val maskedDB = block.slice(hashFunction.length + 1, blockSize)
113 |
114 | val seedMask = mgf(maskedDB, maskedSeed.length)
115 | val seed = maskedSeed xor seedMask
116 |
117 | val dbMask = mgf(seed, maskedDB.length)
118 | val db = maskedDB xor dbMask
119 |
120 | val dbLabel = db.slice(0, hashFunction.length)
121 | if (dbLabel != labelHash) {
122 | error = standardError
123 | }
124 |
125 | val dbPaddedMessage = db.slice(hashFunction.length, db.length)
126 | var index = 0
127 | var indexInPadding = true
128 | var message = Seq[Byte]()
129 |
130 | // This is overly complicated, but continuing on error
131 | // makes side channel attacks to get the specific padding
132 | // error much harder and unreliable.
133 | while (index < dbPaddedMessage.length && indexInPadding) {
134 | val digit = dbPaddedMessage(index)
135 | if (digit != 0.toByte) {
136 | if (digit == 1.toByte) {
137 | indexInPadding = false
138 | message = dbPaddedMessage.slice(index + 1, dbPaddedMessage.length)
139 | } else {
140 | error = standardError
141 | }
142 | }
143 | index += 1
144 | }
145 |
146 | error match {
147 | case Some(e) ⇒
148 | Failure(e)
149 |
150 | case _ ⇒
151 | Success(message)
152 | }
153 | }
154 | }
155 | }
156 |
157 | object OAEP {
158 |
159 | implicit val builder = new CanBuildBlockPadding[OAEP] {
160 | def build(parameters: Parameters): Try[OAEP] = {
161 | val h = Parameters.checkParam[Hash](parameters, 'hash) match {
162 | case Success(hash) ⇒ hash
163 | case Failure(f) ⇒ return Failure(f)
164 | }
165 | val lbl = Parameters.checkParam[Seq[Byte]](parameters, 'label) match {
166 | case Success(label) ⇒ label
167 | case Failure(f) ⇒ return Failure(f)
168 | }
169 | val gen = Parameters.checkParam[(Int) ⇒ Seq[Byte]](parameters, 'generator) match {
170 | case Success(generator) ⇒ generator
171 | case Failure(f) ⇒ return Failure(f)
172 | }
173 | Success(new OAEP {
174 | val hashFunction = h
175 | override val label = lbl
176 | val seedGenerator = gen
177 | val params = parameters
178 | })
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/main/scala/paddings/PKCS7Padding.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.paddings
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | trait PKCS7Padding extends BlockPadding {
21 |
22 | def pad(input: Iterator[Seq[Byte]], blockSize: Int): Iterator[Seq[Byte]] = {
23 | new Iterator[Seq[Byte]] {
24 |
25 | var running = true
26 | var buffer: Seq[Byte] = Seq[Byte]()
27 |
28 | def hasNext: Boolean = running
29 |
30 | def next: Seq[Byte] = {
31 | while (buffer.length < blockSize && input.hasNext) {
32 | buffer = buffer ++ input.next
33 | }
34 |
35 | if (buffer.length >= blockSize) {
36 | val rv = buffer.slice(0, blockSize)
37 | buffer = buffer.slice(blockSize, buffer.length)
38 | rv
39 | } else {
40 | val missing = blockSize - buffer.length
41 | running = false
42 | buffer ++ (for (_ <- 0 until missing) yield missing.toByte)
43 | }
44 | }
45 | }
46 | }
47 |
48 | def unpad(input: Iterator[Seq[Byte]], blockSize: Int): Iterator[Try[Seq[Byte]]] = {
49 | var error: Option[Throwable] = None
50 |
51 | val rv: Iterator[Try[Seq[Byte]]] = new Iterator[Try[Seq[Byte]]] {
52 |
53 | var buffer: Seq[Byte] = if (input.hasNext) {
54 | input.next
55 | } else {
56 | error = Some(new BadPaddingException("Input is empty."))
57 | Seq()
58 | }
59 | if (buffer.length != blockSize) {
60 | error = Some(new IllegalBlockSizeException("BlockPadding.unwrap only accepts an iterator of correct blocks."))
61 | Seq()
62 | }
63 |
64 | def hasNext = buffer.length != 0
65 |
66 | def next: Try[Seq[Byte]] = {
67 | //Peek the next block.
68 | val nextBlock: Seq[Byte] = if (input.hasNext) {
69 | val next = input.next
70 | //Check the block size.
71 | if (next.length != blockSize) {
72 | return Failure(new IllegalBlockSizeException("BlockPadding.unwrap only accepts an iterator of correct blocks."))
73 | }
74 | next
75 | } else {
76 | Seq()
77 | }
78 |
79 | if (input.hasNext) {
80 | //After peeking there is still input left so neither
81 | //buffer nor nextBlock contain the padding.
82 | val rv = buffer
83 | buffer = nextBlock
84 | Success(rv)
85 | } else {
86 | //No input left. Concatenate blocks and remove the padding.
87 | val block = buffer ++ nextBlock
88 | buffer = Seq()
89 |
90 | //Get the padding length
91 | val lastByte = block.last
92 | val paddingLength = if (lastByte.toInt < 0) {
93 | lastByte.toInt + 256
94 | } else {
95 | lastByte.toInt
96 | }
97 |
98 | //Check the padding and return
99 | val padding: Seq[Byte] = for (_ <- 0 until paddingLength) yield lastByte
100 | if (block.slice(block.length - paddingLength, block.length) == padding)
101 | Success(block.slice(0, block.length - paddingLength))
102 | else
103 | Failure(new BadPaddingException("Invalid padding"))
104 | }
105 | }
106 | }
107 |
108 | error match {
109 | case Some(f) ⇒
110 | Iterator(Failure(f))
111 |
112 | case _ ⇒
113 | rv
114 | }
115 | }
116 | }
117 |
118 | object PKCS7Padding {
119 |
120 | implicit val builder = new CanBuildBlockPadding[PKCS7Padding] {
121 | def build(parameters: Parameters) = Success(new PKCS7Padding { val params = parameters })
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/scala/suites/AES_CBC_NoPadding.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.suites
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 | import blockciphers._
20 | import modes._
21 | import paddings._
22 |
23 | object AES128_CBC_NoPadding {
24 |
25 | def apply(key: SymmetricKey128, iv: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey128]] = {
26 | val params = Parameters(
27 | 'symmetricKey128 -> key,
28 | 'iv -> (iv match {
29 | case Some(s) ⇒ s
30 | case _ ⇒ Random.nextBytes(16)
31 | }))
32 |
33 | BlockCipher[AES128](params) flatMap { cipher ⇒
34 | BlockCipherMode[CBC](params) flatMap { mode ⇒
35 | BlockPadding[NoPadding](params) map { padding ⇒
36 | new BlockCipherSuite(cipher, mode, padding)
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 | object AES192_CBC_NoPadding {
44 |
45 | def apply(key: SymmetricKey192, iv: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey192]] = {
46 | val params = Parameters(
47 | 'symmetricKey192 -> key,
48 | 'iv -> (iv match {
49 | case Some(s) ⇒ s
50 | case _ ⇒ Random.nextBytes(16)
51 | }))
52 |
53 | BlockCipher[AES192](params) flatMap { cipher ⇒
54 | BlockCipherMode[CBC](params) flatMap { mode ⇒
55 | BlockPadding[NoPadding](params) map { padding ⇒
56 | new BlockCipherSuite(cipher, mode, padding)
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
63 | object AES256_CBC_NoPadding {
64 |
65 | def apply(key: SymmetricKey256, iv: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey256]] = {
66 | val params = Parameters(
67 | 'symmetricKey256 -> key,
68 | 'iv -> (iv match {
69 | case Some(s) ⇒ s
70 | case _ ⇒ Random.nextBytes(16)
71 | }))
72 |
73 | BlockCipher[AES256](params) flatMap { cipher ⇒
74 | BlockCipherMode[CBC](params) flatMap { mode ⇒
75 | BlockPadding[NoPadding](params) map { padding ⇒
76 | new BlockCipherSuite(cipher, mode, padding)
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/scala/suites/AES_CBC_PKCS7Padding.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.suites
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 | import blockciphers._
20 | import modes._
21 | import paddings._
22 |
23 | object AES128_CBC_PKCS7Padding {
24 |
25 | def apply(key: SymmetricKey128, iv: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey128]] = {
26 | val params = Parameters(
27 | 'symmetricKey128 -> key,
28 | 'iv -> (iv match {
29 | case Some(s) ⇒ s
30 | case _ ⇒ Random.nextBytes(16)
31 | }))
32 |
33 | BlockCipher[AES128](params) flatMap { cipher ⇒
34 | BlockCipherMode[CBC](params) flatMap { mode ⇒
35 | BlockPadding[PKCS7Padding](params) map { padding ⇒
36 | new BlockCipherSuite(cipher, mode, padding)
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 | object AES192_CBC_PKCS7Padding {
44 |
45 | def apply(key: SymmetricKey192, iv: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey192]] = {
46 | val params = Parameters(
47 | 'symmetricKey192 -> key,
48 | 'iv -> (iv match {
49 | case Some(s) ⇒ s
50 | case _ ⇒ Random.nextBytes(16)
51 | }))
52 |
53 | BlockCipher[AES192](params) flatMap { cipher ⇒
54 | BlockCipherMode[CBC](params) flatMap { mode ⇒
55 | BlockPadding[PKCS7Padding](params) map { padding ⇒
56 | new BlockCipherSuite(cipher, mode, padding)
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
63 | object AES256_CBC_PKCS7Padding {
64 |
65 | def apply(key: SymmetricKey256, iv: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey256]] = {
66 | val params = Parameters(
67 | 'symmetricKey256 -> key,
68 | 'iv -> (iv match {
69 | case Some(s) ⇒ s
70 | case _ ⇒ Random.nextBytes(16)
71 | }))
72 |
73 | BlockCipher[AES256](params) flatMap { cipher ⇒
74 | BlockCipherMode[CBC](params) flatMap { mode ⇒
75 | BlockPadding[PKCS7Padding](params) map { padding ⇒
76 | new BlockCipherSuite(cipher, mode, padding)
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/scala/suites/RSAES_OAEP.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.suites
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 | import hash._
20 | import blockciphers._
21 | import modes._
22 | import paddings._
23 |
24 | object RSAES_OAEP {
25 |
26 | def apply(k: RSAKey, label: Seq[Byte] = Seq[Byte](), hash: Hash = SHA256, genSeed: (Int) ⇒ Seq[Byte] = { length ⇒ Random.nextBytes(length) }): Try[BlockCipherSuite[RSAKey]] = {
27 | val params = Parameters(
28 | 'rsaKey -> k,
29 | 'label -> label,
30 | 'hash -> hash,
31 | 'generator -> genSeed)
32 |
33 | BlockCipher[RSA](params) flatMap { cipher ⇒
34 | BlockCipherMode[ECB](params) flatMap { mode ⇒
35 | BlockPadding[OAEP](params) map { padding ⇒
36 | new BlockCipherSuite(cipher, mode, padding)
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/scala/suites/Threefish_CBC_PKCS7Padding.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.suites
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 | import blockciphers._
20 | import modes._
21 | import paddings._
22 |
23 | object Threefish256_CBC_PKCS7Padding {
24 |
25 | def apply(key: SymmetricKey256, iv: Option[Seq[Byte]] = None, tweak: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey256]] = {
26 | val params = Parameters(
27 | 'symmetricKey256 -> key,
28 | 'iv -> (iv match {
29 | case Some(s) ⇒ s
30 | case _ ⇒ Random.nextBytes(32)
31 | }),
32 | 'tweak -> (tweak match {
33 | case Some(s) ⇒ s
34 | case _ ⇒ Random.nextBytes(16)
35 | }))
36 |
37 | BlockCipher[Threefish256](params) flatMap { cipher ⇒
38 | BlockCipherMode[CBC](params) flatMap { mode ⇒
39 | BlockPadding[PKCS7Padding](params) map { padding ⇒
40 | new BlockCipherSuite(cipher, mode, padding)
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
47 | object Threefish512_CBC_PKCS7Padding {
48 |
49 | def apply(key: SymmetricKey512, iv: Option[Seq[Byte]] = None, tweak: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey512]] = {
50 | val params = Parameters(
51 | 'symmetricKey512 -> key,
52 | 'iv -> (iv match {
53 | case Some(s) ⇒ s
54 | case _ ⇒ Random.nextBytes(64)
55 | }),
56 | 'tweak -> (tweak match {
57 | case Some(s) ⇒ s
58 | case _ ⇒ Random.nextBytes(16)
59 | }))
60 |
61 | BlockCipher[Threefish512](params) flatMap { cipher ⇒
62 | BlockCipherMode[CBC](params) flatMap { mode ⇒
63 | BlockPadding[PKCS7Padding](params) map { padding ⇒
64 | new BlockCipherSuite(cipher, mode, padding)
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
71 | object Threefish1024_CBC_PKCS7Padding {
72 |
73 | def apply(key: SymmetricKey1024, iv: Option[Seq[Byte]] = None, tweak: Option[Seq[Byte]] = None): Try[BlockCipherSuite[SymmetricKey1024]] = {
74 | val params = Parameters(
75 | 'symmetricKey1024 -> key,
76 | 'iv -> (iv match {
77 | case Some(s) ⇒ s
78 | case _ ⇒ Random.nextBytes(128)
79 | }),
80 | 'tweak -> (tweak match {
81 | case Some(s) ⇒ s
82 | case _ ⇒ Random.nextBytes(16)
83 | }))
84 |
85 | BlockCipher[Threefish1024](params) flatMap { cipher ⇒
86 | BlockCipherMode[CBC](params) flatMap { mode ⇒
87 | BlockPadding[PKCS7Padding](params) map { padding ⇒
88 | new BlockCipherSuite(cipher, mode, padding)
89 | }
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/scala/util/PBKDF2Easy.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt.util
16 |
17 | import xyz.wiedenhoeft.scalacrypt._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | /**
21 | * Implements an easy way to hash passwords using PBKDF2.
22 | *
23 | * The verification process is backwards compatible.
24 | */
25 | object PBKDF2Easy {
26 | lazy val algoMap = Map[Byte, KeyedHash[Key]](
27 | 1.toByte -> khash.HmacSHA256)
28 |
29 | lazy val defaultAlgorithm = 1.toByte
30 | val defaultSaltLength = 32
31 | val defaultHashLength = 32
32 |
33 | lazy val defaultSaltLengthBytes = java.nio.ByteBuffer.allocate(4).putInt(defaultSaltLength).array.toList
34 | lazy val defaultHashLengthBytes = java.nio.ByteBuffer.allocate(4).putInt(defaultHashLength).array.toList
35 |
36 | def apply(password: Seq[Byte], iterations: Int = 20000): Try[Seq[Byte]] = {
37 | val key = password.toKey[SymmetricKeyArbitrary].get
38 | val iterationsBytes = java.nio.ByteBuffer.allocate(4).putInt(iterations).array.toList
39 | val pbkdf2 = khash.PBKDF2(algoMap(defaultAlgorithm), iterations, defaultHashLength)
40 |
41 | val salt = Random.nextBytes(32).toList
42 | pbkdf2(key, salt) map { _.toList } match {
43 | case Success(hash) ⇒
44 | Success(defaultAlgorithm :: iterationsBytes ::: defaultSaltLengthBytes ::: salt ::: defaultHashLengthBytes ::: hash)
45 |
46 | case Failure(f) ⇒
47 | Failure(f)
48 | }
49 | }
50 |
51 | def verify(password: Seq[Byte], hash: Seq[Byte]): Try[Boolean] = {
52 | if (hash.length < 9 || !algoMap.contains(hash(0))) return Success(false)
53 |
54 | val key = password.toKey[SymmetricKeyArbitrary].get
55 | val algorithm = algoMap(hash(0))
56 | val iterations = java.nio.ByteBuffer.allocate(4).put(hash.slice(1, 5).toArray).getInt(0)
57 | val saltLength = java.nio.ByteBuffer.allocate(4).put(hash.slice(5, 9).toArray).getInt(0)
58 |
59 | val slice1 = hash.slice(9, hash.length)
60 | if (slice1.length < saltLength) return Success(false)
61 |
62 | val salt = slice1.slice(0, saltLength)
63 |
64 | val slice2 = slice1.slice(saltLength, slice1.length)
65 | if (slice2.length < 4) return Success(false)
66 |
67 | val hashLength = java.nio.ByteBuffer.allocate(4).put(slice2.slice(0, 4).toArray).getInt(0)
68 |
69 | val realHash = slice2.slice(4, slice2.length)
70 | if (realHash.length != hashLength) return Success(false)
71 |
72 | val pbkdf2 = khash.PBKDF2(algorithm, iterations, hashLength)
73 | pbkdf2(key, salt) map { _ == realHash }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/test/scala/BlockCipherSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import blockciphers._
19 | import scala.util.{ Try, Success, Failure }
20 | import scala.reflect._
21 |
22 | abstract class BlockCipherSpec[KeyType <: Key: CanGenerateKey, Cipher <: BlockCipher[KeyType]: CanBuildBlockCipher: ClassTag] extends FlatSpec with Matchers {
23 |
24 | /**
25 | * Basic parameters that are sufficient to construct the cipher.
26 | *
27 | * While processing test vectors these also provide a scaffolding
28 | * to construct the instances.
29 | */
30 | def baseParameters: Parameters
31 |
32 | /** Symbol that is used to add the key to the baseParameters. */
33 | def keySymbol: Symbol
34 |
35 | /** Sets of parameters and whether construction should succeed. */
36 | def parameterTestVectors: Seq[(Parameters, Boolean)]
37 |
38 | /** Encryption and decryption test vectors: (cleartext, key, ciphertext, additional params) */
39 | def testVectors: Seq[(Seq[Byte], KeyType, Seq[Byte], Option[Parameters])]
40 |
41 | /** Block size this cipher should have */
42 | def blockSize: Int
43 |
44 | /**
45 | * When encrypting and decrypting a random block this is prepended to the random block.
46 | *
47 | * It is needed for example by RSA where the numerical message representation must be less than
48 | * the modulus of the key.
49 | */
50 | def firstBytesOfRandomBlock: Seq[Byte] = Seq()
51 |
52 | val cipherName = classTag[Cipher].runtimeClass.getName.split('.').last
53 |
54 | cipherName should "be buildable using type classes." in {
55 | BlockCipher[Cipher](baseParameters).get
56 | }
57 |
58 | it should "pass the parameter test vectors." in {
59 | for (vector <- parameterTestVectors) {
60 | val opt = BlockCipher[Cipher](vector._1)
61 | if (vector._2) opt shouldBe a[Success[_]]
62 | else opt shouldBe a[Failure[_]]
63 | }
64 | }
65 |
66 | it should "be able to encrypt and decrypt a random bytestring." in {
67 | val cipher = BlockCipher[Cipher](baseParameters).get
68 | val m = firstBytesOfRandomBlock ++ Random.nextBytes(cipher.blockSize - firstBytesOfRandomBlock.length)
69 | val c = cipher.encryptBlock(m).get
70 | cipher.decryptBlock(c).get should be(m)
71 | }
72 |
73 | it should "pass the encryption test vectors." in {
74 | for (vector <- testVectors) {
75 | val m = vector._1
76 | val k = vector._2
77 | val c = vector._3
78 | val pOpt = vector._4
79 |
80 | val params = baseParameters ++ Parameters(keySymbol -> k) ++ (if (pOpt.isDefined) pOpt.get else Parameters())
81 | val cipher = BlockCipher[Cipher](params).get
82 | cipher.encryptBlock(m).get should be(c)
83 | cipher.decryptBlock(c).get should be(m)
84 | }
85 | }
86 |
87 | it should "fail on invalid block sizes." in {
88 | val cipher = BlockCipher[Cipher](baseParameters).get
89 | cipher.encryptBlock(Random.nextBytes(cipher.blockSize - 1)) shouldBe a[Failure[_]]
90 | cipher.decryptBlock(Random.nextBytes(cipher.blockSize - 1)) shouldBe a[Failure[_]]
91 | cipher.encryptBlock(Random.nextBytes(cipher.blockSize + 1)) shouldBe a[Failure[_]]
92 | cipher.decryptBlock(Random.nextBytes(cipher.blockSize + 1)) shouldBe a[Failure[_]]
93 | }
94 |
95 | it should "have the correct block size" in {
96 | val cipher = BlockCipher[Cipher](baseParameters).get
97 | cipher.blockSize should be(blockSize)
98 | }
99 | }
100 |
101 | class AES128Spec extends BlockCipherSpec[SymmetricKey128, AES128] {
102 |
103 | val baseParameters = Parameters('symmetricKey128 -> Key.generate[SymmetricKey128])
104 | val keySymbol = 'symmetricKey128
105 | val blockSize = 16
106 |
107 | val parameterTestVectors = Seq(
108 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey256]), false),
109 | (Parameters('symmetricKey128 -> Key.generate[SymmetricKey192]), false))
110 |
111 | val defaultKey = (Seq(
112 | 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
113 | 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c) map { _.toByte }).toKey[SymmetricKey128].get
114 |
115 | val testVectors = Seq(
116 | (
117 | Seq(0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a) map { _.toByte },
118 | defaultKey,
119 | Seq(0x3a, 0xd7, 0x7b, 0xb4, 0x0d, 0x7a, 0x36, 0x60, 0xa8, 0x9e, 0xca, 0xf3, 0x24, 0x66, 0xef, 0x97) map { _.toByte },
120 | None), (
121 | Seq(0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51) map { _.toByte },
122 | defaultKey,
123 | Seq(0xf5, 0xd3, 0xd5, 0x85, 0x03, 0xb9, 0x69, 0x9d, 0xe7, 0x85, 0x89, 0x5a, 0x96, 0xfd, 0xba, 0xaf) map { _.toByte },
124 | None), (
125 | Seq(0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef) map { _.toByte },
126 | defaultKey,
127 | Seq(0x43, 0xb1, 0xcd, 0x7f, 0x59, 0x8e, 0xce, 0x23, 0x88, 0x1b, 0x00, 0xe3, 0xed, 0x03, 0x06, 0x88) map { _.toByte },
128 | None), (
129 | Seq(0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10) map { _.toByte },
130 | defaultKey,
131 | Seq(0x7b, 0x0c, 0x78, 0x5e, 0x27, 0xe8, 0xad, 0x3f, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5d, 0xd4) map { _.toByte },
132 | None))
133 | }
134 |
135 | class AES192Spec extends BlockCipherSpec[SymmetricKey192, AES192] {
136 | val baseParameters = Parameters('symmetricKey192 -> Key.generate[SymmetricKey192])
137 | val keySymbol = 'symmetricKey192
138 | val blockSize = 16
139 |
140 | val parameterTestVectors = Seq(
141 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey256]), false),
142 | (Parameters('symmetricKey192 -> Key.generate[SymmetricKey1024]), false))
143 |
144 | val testVectors = Seq()
145 | }
146 |
147 | class AES256Spec extends BlockCipherSpec[SymmetricKey256, AES256] {
148 | val baseParameters = Parameters('symmetricKey256 -> Key.generate[SymmetricKey256])
149 | val keySymbol = 'symmetricKey256
150 | val blockSize = 16
151 |
152 | val parameterTestVectors = Seq(
153 | (Parameters('symmetricKey128 -> Key.generate[SymmetricKey128]), false),
154 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey128]), false))
155 |
156 | val testVectors = Seq()
157 | }
158 |
159 | class Threefish256Spec extends BlockCipherSpec[SymmetricKey256, Threefish256] {
160 | val baseParameters = Parameters('symmetricKey256 -> Key.generate[SymmetricKey256], 'tweak -> (0 until 16 map { _.toByte }))
161 | val keySymbol = 'symmetricKey256
162 | val tweak = (0 until 16) map { _.toByte }
163 | val blockSize = 32
164 |
165 | val parameterTestVectors = Seq(
166 | (Parameters('symmetricKey128 -> Key.generate[SymmetricKey128], 'tweak -> tweak), false),
167 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey1024], 'tweak -> tweak), false),
168 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey256], 'tweak -> (0 until 15 map { _.toByte })), false))
169 |
170 | val testVectors = Seq()
171 | }
172 |
173 | class Threefish512Spec extends BlockCipherSpec[SymmetricKey512, Threefish512] {
174 | val baseParameters = Parameters('symmetricKey512 -> Key.generate[SymmetricKey512], 'tweak -> (0 until 16 map { _.toByte }))
175 | val keySymbol = 'symmetricKey512
176 | val tweak = (0 until 16) map { _.toByte }
177 | val blockSize = 64
178 |
179 | val parameterTestVectors = Seq(
180 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey256], 'tweak -> tweak), false),
181 | (Parameters('symmetricKey512 -> Key.generate[SymmetricKey1024], 'tweak -> tweak), false),
182 | (Parameters('symmetricKey512 -> Key.generate[SymmetricKey512], 'tweak -> (0 until 15 map { _.toByte })), false))
183 |
184 | val testVectors = Seq()
185 | }
186 |
187 | class Threefish1024Spec extends BlockCipherSpec[SymmetricKey1024, Threefish1024] {
188 | val baseParameters = Parameters('symmetricKey1024 -> Key.generate[SymmetricKey1024], 'tweak -> (0 until 16 map { _.toByte }))
189 | val keySymbol = 'symmetricKey1024
190 | val tweak = (0 until 16) map { _.toByte }
191 | val blockSize = 128
192 |
193 | val parameterTestVectors = Seq(
194 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey256], 'tweak -> tweak), false),
195 | (Parameters('symmetricKey1024 -> Key.generate[SymmetricKey256], 'tweak -> tweak), false),
196 | (Parameters('symmetricKey512 -> Key.generate[SymmetricKey512], 'tweak -> (0 until 15 map { _.toByte })), false))
197 |
198 | val testVectors = Seq()
199 | }
200 |
201 | class RSACrtSpec extends BlockCipherSpec[RSAKey, RSA] {
202 |
203 | val crtKey =
204 | """ |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
245 | |1KJvCOgIoxKqFtV+O7c=""".stripMargin.toBase64Bytes.toKey[RSAKey].get
246 |
247 | val baseParameters = Parameters('rsaKey -> crtKey)
248 | val keySymbol = 'rsaKey
249 | val blockSize = 512
250 | override val firstBytesOfRandomBlock = Seq(0, 0, 0, 0) map { _.toByte }
251 |
252 | val parameterTestVectors = Seq(
253 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey256]), false),
254 | (Parameters('rsaKey -> Key.generate[SymmetricKey1024]), false))
255 |
256 | val testVectors = Seq()
257 | }
258 |
259 | class RSAExpSpec extends BlockCipherSpec[RSAKey, RSA] {
260 |
261 | val crtKey =
262 | """ |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
303 | |1KJvCOgIoxKqFtV+O7c=""".stripMargin.toBase64Bytes.toKey[RSAKey].get
304 |
305 | val expKey = (crtKey.e.toByteArray.toSeq, crtKey.privateKey.get.asInstanceOf[RSAPrivateCombinedKeyPart].d.toByteArray.toSeq, crtKey.n.toByteArray.toSeq).toKey[RSAKey].get
306 |
307 | val baseParameters = Parameters('rsaKey -> expKey)
308 | val keySymbol = 'rsaKey
309 | val blockSize = 512
310 | override val firstBytesOfRandomBlock = Seq(0, 0, 0, 0) map { _.toByte }
311 |
312 | val parameterTestVectors = Seq(
313 | (Parameters('symmetricKey256 -> Key.generate[SymmetricKey256]), false),
314 | (Parameters('rsaKey -> Key.generate[SymmetricKey1024]), false))
315 |
316 | val testVectors = Seq()
317 | }
318 |
--------------------------------------------------------------------------------
/src/test/scala/BlockPaddingSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 | import paddings._
20 |
21 | class BlockPaddingSpec extends FlatSpec with Matchers {
22 |
23 | "PKCS7Padding" should "pad and unpad data correctly" in {
24 | val testvectors = Seq[(Int, Seq[Seq[Byte]], Seq[Seq[Byte]])](
25 | (
26 | 16,
27 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
28 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 6, 6, 6, 6, 6, 6))), (
29 | 16,
30 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)),
31 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), Seq(16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16))), (
32 | 16,
33 | Seq(Seq()),
34 | Seq(Seq(16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16))), (
35 | 8,
36 | Seq(Seq(1, 2, 3, 4, 5, 6)),
37 | Seq(Seq(1, 2, 3, 4, 5, 6, 2, 2))), (
38 | 8,
39 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8)),
40 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8), Seq(8, 8, 8, 8, 8, 8, 8, 8))), (
41 | 8,
42 | Seq(Seq()),
43 | Seq(Seq(8, 8, 8, 8, 8, 8, 8, 8))), (
44 | 16,
45 | Seq(Seq(1, 2, 3), Seq(4, 5, 6), Seq(7, 8, 9), Seq(10)),
46 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 6, 6, 6, 6, 6, 6))))
47 |
48 | for (testvector <- testvectors) {
49 | val padding = BlockPadding[PKCS7Padding](Parameters()).get
50 | padding.pad(testvector._2.toIterator, testvector._1).toSeq should be(testvector._3)
51 | padding.unpad(testvector._3.toIterator, testvector._1).toSeq.map({ _.get }).flatten should be(testvector._2.flatten)
52 | }
53 | }
54 |
55 | it should "return an error when invalid padding is encountered" in {
56 | val tests = Seq[(Int, Seq[Seq[Byte]])](
57 | (
58 | // Does not contain padding block
59 | 8,
60 | Seq(Seq(1, 2, 3, 4, 5, 6, 7, 8), Seq(1, 2, 3, 4, 5, 6, 7, 8))), (
61 | // Wrong padding byte
62 | 8,
63 | Seq(Seq(1, 2, 3, 4, 5, 6, 3, 3))), (
64 | // Illegal block size
65 | 8,
66 | Seq(Seq(1, 2, 3, 4, 5, 6, 7), Seq(1))), (
67 | // Illegal block size
68 | 8,
69 | Seq(Seq(1, 2, 3), Seq(8, 8, 8, 8, 8, 8, 8, 8))), (
70 | // Wrong byte inside padding
71 | 8,
72 | Seq(Seq(1, 2, 6, 6, 6, 7, 6, 6))))
73 |
74 | for (test <- tests) {
75 | val padding = BlockPadding[PKCS7Padding](Parameters()).get
76 | try {
77 | padding.unpad(test._2.toIterator, test._1).toSeq.filter({ _.isFailure }).headOption shouldBe a[Some[_]]
78 | } catch {
79 | case t: Throwable ⇒
80 | fail(t.getMessage + " :: " + test.toString)
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/test/scala/HashSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import hash._
19 |
20 | class HashSpec extends FlatSpec with Matchers {
21 |
22 | "SHA1" should "conform to the testvectors" in {
23 | val vectors = Seq(
24 | (
25 | "".getBytes.toSeq,
26 | Seq(0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09)), (
27 | "abc".getBytes.toSeq,
28 | Seq(0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d)), (
29 | "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu".getBytes.toSeq,
30 | Seq(0xa4, 0x9b, 0x24, 0x46, 0xa0, 0x2c, 0x64, 0x5b, 0xf4, 0x19, 0xf9, 0x95, 0xb6, 0x70, 0x91, 0x25, 0x3a, 0x04, 0xa2, 0x59)), (
31 | Seq.fill[Byte](1000000) { 'a'.toByte },
32 | Seq(0x34, 0xaa, 0x97, 0x3c, 0xd4, 0xc4, 0xda, 0xa4, 0xf6, 0x1e, 0xeb, 0x2b, 0xdb, 0xad, 0x27, 0x31, 0x65, 0x34, 0x01, 0x6f)))
33 |
34 | for (vector <- vectors) {
35 | val hash = SHA1(vector._1)
36 | val expected = vector._2 map { _.toByte }
37 | hash should be(expected)
38 | }
39 | }
40 |
41 | "SHA256" should "conform to the testvectors" in {
42 | val vectors = Seq(
43 | (
44 | "".getBytes.toSeq,
45 | Seq(0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55)), (
46 | "abc".getBytes.toSeq,
47 | Seq(0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad)), (
48 | "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu".getBytes.toSeq,
49 | Seq(0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80, 0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37, 0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51, 0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1)), (
50 | Seq.fill[Byte](1000000) { 'a'.toByte },
51 | Seq(0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7, 0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97, 0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0)))
52 |
53 | for (vector <- vectors) {
54 | val hash = SHA256(vector._1)
55 | val expected = vector._2 map { _.toByte }
56 | hash should be(expected)
57 | }
58 | }
59 |
60 | "The hashes" should "conform to the testvectors" in {
61 | val testbytes = "The quick brown fox jumps over the lazy dog".getBytes.toSeq
62 | val md5 = MD5(testbytes)
63 | val sha1 = SHA1(testbytes)
64 | val sha256 = SHA256(testbytes)
65 |
66 | md5 should be(Seq(0x9e, 0x10, 0x7d, 0x9d, 0x37, 0x2b, 0xb6, 0x82, 0x6b, 0xd8, 0x1d, 0x35, 0x42, 0xa4, 0x19, 0xd6) map { _.toByte })
67 | sha1 should be(Seq(0x2f, 0xd4, 0xe1, 0xc6, 0x7a, 0x2d, 0x28, 0xfc, 0xed, 0x84, 0x9e, 0xe1, 0xbb, 0x76, 0xe7, 0x39, 0x1b, 0x93, 0xeb, 0x12) map { _.toByte })
68 | sha256 should be(Seq(0xd7, 0xa8, 0xfb, 0xb3, 0x07, 0xd7, 0x80, 0x94, 0x69, 0xca, 0x9a, 0xbc, 0xb0, 0x08, 0x2e, 0x4f, 0x8d, 0x56, 0x51, 0xe4, 0x6d, 0x3c, 0xdb, 0x76, 0x2d, 0x02, 0xd0, 0xbf, 0x37, 0xc9, 0xe5, 0x92) map { _.toByte })
69 | }
70 |
71 | "The hash lengths" should "be right." in {
72 | MD5.length should be(16)
73 | SHA1.length should be(20)
74 | SHA256.length should be(32)
75 | }
76 |
77 | "Hashes" should "be able to process an iterator returning a future." in {
78 | val testbytes = "The quick brown fox jumps over the lazy dog".getBytes.toIterator.grouped(5)
79 |
80 | val (iterator, futureHash) = SHA256(testbytes)
81 | // Empty the iterator so the promise gets completed
82 | while (iterator.hasNext) iterator.next
83 |
84 | futureHash.isCompleted should be(true)
85 | futureHash.value.get.get should be(Seq(0xd7, 0xa8, 0xfb, 0xb3, 0x07, 0xd7, 0x80, 0x94, 0x69, 0xca, 0x9a, 0xbc, 0xb0, 0x08, 0x2e, 0x4f, 0x8d, 0x56, 0x51, 0xe4, 0x6d, 0x3c, 0xdb, 0x76, 0x2d, 0x02, 0xd0, 0xbf, 0x37, 0xc9, 0xe5, 0x92) map { _.toByte })
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/test/scala/IterateeSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import iteratees._
18 | import org.scalatest._
19 | import scala.util.{ Try, Success, Failure }
20 |
21 | class IterateeSpec extends FlatSpec with Matchers {
22 | val concatProto = Iteratee.fold[String, String]("") { (a, e) ⇒
23 | Success(a ++ e)
24 | }
25 |
26 | "An Iteratee" should "be able to concat strings." in {
27 | var concat = concatProto
28 | concat = concat.fold(Element("Hello "))
29 | concat = concat.fold(Empty)
30 | concat = concat.fold(Element("world"))
31 | val res = concat.run
32 |
33 | res shouldBe a[Success[_]]
34 | res.get should be("Hello world")
35 | }
36 |
37 | it should "have a working map method" in {
38 | var counter = concatProto map { str ⇒
39 | str.length
40 | }
41 |
42 | counter = counter.fold(Element("Hello "))
43 | counter = counter.fold(Empty)
44 | counter = counter.fold(Element("world"))
45 | val res = counter.run
46 |
47 | res shouldBe a[Success[_]]
48 | res.get should be(11)
49 | }
50 |
51 | val enumHello = Enumerator("Hello ", "world")
52 |
53 | "An Enumerator" should "be applicable to an iteratee." in {
54 | enumHello.run(concatProto).get should be("Hello world")
55 | }
56 |
57 | it should "have a working flatMap method." in {
58 | val separators = enumHello.flatMap { e ⇒
59 | new Enumerator[String] {
60 | def apply[A](iteratee: Iteratee[String, A]) = {
61 | iteratee.fold(Element(e)).fold(Element("/"))
62 | }
63 | }
64 | }
65 |
66 | separators.run(concatProto).get should be("Hello /world/")
67 | }
68 |
69 | it should "have a working map method." in {
70 | val counts = enumHello map { _.length.toString + " " }
71 | counts.run(concatProto).get should be("6 5 ")
72 | }
73 |
74 | val sum = Iteratee.fold(0) { (a: Int, e: Int) ⇒ Success(a + e) }
75 | val toInt = Enumeratee.map { (str: String) ⇒ str.toInt }
76 |
77 | val intEnum1 = Enumerator(5, 5, 1) // Sum 11
78 | val stringEnum = Enumerator("2", "4", "6") // Sum 12 / 23
79 | val intEnum2 = Enumerator(3, 4) // Sum 7 / 30
80 |
81 | "An Enumeratee" should "be able to map input to an Iteratee" in {
82 | val sum1 = intEnum1(sum)
83 | val sum2 = toInt(sum1)
84 | val sum3 = stringEnum(sum2)
85 | val sum4 = sum3.run.get
86 | val sum5 = intEnum2(sum4)
87 | sum5.run.get should be(30)
88 | }
89 |
90 | it should "be able to transform an Iteratee" in {
91 | val sum1 = intEnum1(sum)
92 | val sum2 = toInt.transform(sum1)
93 | val sum3 = stringEnum(sum2)
94 | sum3.run.get should be(23)
95 | }
96 |
97 | "Iteratee.done" should "return an Iteratee which is Done." in {
98 | val iteratee = Iteratee.done[Any, Boolean](true)
99 | iteratee.state shouldBe a[Done[_, _]]
100 | iteratee.run.get should be(true)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/test/scala/KeyedHashSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 | import khash._
20 | import iteratees._
21 |
22 | class KeyedHashSpec extends FlatSpec with Matchers {
23 |
24 | "HmacSha256" should "be consistent with the test vectors." in {
25 | val key1: Key = Seq[Byte](0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b).toKey[SymmetricKeyArbitrary].get
26 | val data1: Seq[Byte] = Seq(0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65) map { _.toByte }
27 | val hmac1: Seq[Byte] = Seq(0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7) map { _.toByte }
28 | val myMac1 = HmacSHA256(key1, data1).get
29 | myMac1 should be(hmac1)
30 | HmacSHA256.verify(key1, data1, hmac1).get should be(true)
31 |
32 | val key2: Key = Seq(0x4a, 0x65, 0x66, 0x65).map({ _.toByte }).toKey[SymmetricKeyArbitrary].get
33 | val data2: Seq[Byte] = Seq(0x77, 0x68, 0x61, 0x74, 0x20, 0x64, 0x6f, 0x20, 0x79, 0x61, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x3f) map { _.toByte }
34 | val hmac2: Seq[Byte] = Seq(0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75, 0xc7, 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43) map { _.toByte }
35 | val myMac2 = HmacSHA256(key2, data2).get
36 | myMac2 should be(hmac2)
37 | HmacSHA256.verify(key2, data2, hmac2).get should be(true)
38 | }
39 |
40 | it should "not fail on keys with length zero." in {
41 | HmacSHA256(Seq[Byte]().toKey[SymmetricKeyArbitrary].get, Seq[Byte](1, 2, 3)).get
42 | }
43 |
44 | it should "not fail on data with length zero." in {
45 | HmacSHA256(Seq[Byte](1, 2, 3).toKey[SymmetricKeyArbitrary].get, Seq()).get
46 | }
47 |
48 | it should "have the same output for a zero key and an empty key" in {
49 | val k1 = Seq[Byte]().toKey[SymmetricKeyArbitrary].get
50 | val k2 = Seq[Byte](0, 0, 0, 0, 0).toKey[SymmetricKeyArbitrary].get
51 | val data = "abcdefg".getBytes
52 | val h1 = HmacSHA256(k1, data).get
53 | val h2 = HmacSHA256(k2, data).get
54 | h1 should be(h2)
55 | }
56 |
57 | it should "be able to process iterators without consuming them." in {
58 | val key = Key.generate[SymmetricKeyArbitrary]
59 | val seq = Seq(Seq(1, 2, 3), Seq(4, 5, 6), Seq(7, 8, 9)) map { _.map { _.toByte } }
60 | val data = seq.flatMap { e ⇒ e }
61 |
62 | val mac = HmacSHA256(key, data).get
63 | var option: Option[Try[Seq[Byte]]] = None
64 |
65 | val (iterator, future) = HmacSHA256(key, seq.toIterator).get
66 | iterator.toSeq should be(seq)
67 | future.value.get.get should be(mac)
68 | }
69 |
70 | "The iteratee of a KeyedHash" should "be branchable." in {
71 | val key = Key.generate[SymmetricKeyArbitrary]
72 | val base = HmacSHA256(key).get.fold(Element(Seq(1, 2, 3) map { _.toByte }))
73 | val branch1Result = base.fold(Element(Seq(4, 5, 6) map { _.toByte })).run.get
74 | val branch2Result = base.fold(Element(Seq(7, 8, 9) map { _.toByte })).run.get
75 |
76 | branch1Result should be(HmacSHA256(key, Seq(1, 2, 3, 4, 5, 6) map { _.toByte }).get)
77 | branch2Result should be(HmacSHA256(key, Seq(1, 2, 3, 7, 8, 9) map { _.toByte }).get)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/scala/PBKDF2Spec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 | import khash._
20 | import util._
21 |
22 | class PBKDF2Spec extends FlatSpec with Matchers {
23 |
24 | "PBKDF2" should "be consistent with the test vectors." in {
25 | val tests = Seq[(String, String, Int, Seq[Byte])](
26 | (
27 | "password",
28 | "salt",
29 | 1,
30 | Seq(0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06, 0x2f, 0xe0, 0x37, 0xa6) map { _.toByte }), (
31 | "password",
32 | "salt",
33 | 2,
34 | Seq(0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c, 0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0, 0xd8, 0xde, 0x89, 0x57) map { _.toByte }), (
35 | "password",
36 | "salt",
37 | 4096,
38 | Seq(0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a, 0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0, 0x65, 0xa4, 0x29, 0xc1) map { _.toByte }), (
39 | "passwordPASSWORDpassword",
40 | "saltSALTsaltSALTsaltSALTsaltSALTsalt",
41 | 4096,
42 | Seq(0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b, 0x80, 0xc8, 0xd8, 0x36, 0x62, 0xc0, 0xe4, 0x4a, 0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70, 0x38) map { _.toByte }))
43 |
44 | for (test <- tests) {
45 | val instance = PBKDF2(HmacSHA1, test._3, test._4.length)
46 | val key = test._1.getBytes.toSeq.toKey[SymmetricKeyArbitrary].get
47 | instance(key, test._2.getBytes.toSeq).get should be(test._4)
48 | }
49 | }
50 |
51 | "PBKDF2Easy" should "verify hashes in a backwards compatible manner." in {
52 | val password = "password".getBytes
53 | val hash = PBKDF2Easy(password).get
54 | PBKDF2Easy.verify(password, hash).get should be(true)
55 |
56 | // Whenever the default values are changed somehow add a test for it here.
57 | PBKDF2Easy.verify(password, "AQAATiAAAAAgFGporonZGhmbMTQidJPd8LHxyq+JzyiW8ivwfrgoZ6sAAAAgMdVuGSQgga7QpQGbqom+3EKhxioJEEwGAxngt/9UifM=".toBase64Bytes).get should be(true)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/scala/RSASpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 | import blockciphers._
20 |
21 | class RSASpec extends FlatSpec with Matchers {
22 | val testKey =
23 | """ |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
50 | |5zIfjLTf4x6AEgvDsQltpLsCxSXGx4c5w4PfRQ==""".stripMargin.toBase64Bytes.toKey[RSAKey].get
51 |
52 | val testKey2 =
53 | """ |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
94 | |WbN8wknG5lqLs7Cu7w==""".stripMargin.toBase64Bytes.toKey[RSAKey].get
95 |
96 | "A generated RSAKey" should "be serializable." in {
97 | val bytes = testKey.bytes
98 | val bytes2 = testKey2.bytes
99 | val newKey = bytes.toKey[RSAKey].get
100 | val newKey2 = bytes2.toKey[RSAKey].get
101 | newKey should be(testKey)
102 | newKey2 should be(testKey2)
103 | }
104 |
105 | it should "have length 512." in {
106 | testKey.length should be(512)
107 | testKey2.length should be(512)
108 | }
109 |
110 | it should "export the public part." in {
111 | testKey.isPrivateKey should be(true)
112 | val pubKey = testKey.publicKey
113 | pubKey.isPrivateKey should be(false)
114 | pubKey.privateKey should be(None)
115 | }
116 |
117 | "RSAES_OAEP encryption" should "correctly encrypt and decrypt data" in {
118 | val suite = suites.RSAES_OAEP(testKey).get
119 | val suite2 = suites.RSAES_OAEP(testKey2).get
120 | val test = (0 until 16) map { _.toByte }
121 | val c = suite.encrypt(test).get
122 | val c2 = suite2.encrypt(test).get
123 | suite.decrypt(c).get should be(test)
124 | suite2.decrypt(c2).get should be(test)
125 | }
126 |
127 | it should "not fail on certain data inputs." in {
128 | val test = (Seq.fill[Byte](512 - 64) { 0.toByte }) ++ "AmzVJLEIo/6xoaqpZ6G5SutGJ8Rxh5Mk9mPhnuj+CBDnp+BE4jITQo1wtzFOLjQnwSp/nmK9zScDJoDsWYk9CA==".toBase64Bytes
129 | val params1 = Parameters('rsaKey -> testKey)
130 | val params2 = Parameters('rsaKey -> testKey2)
131 | val rsa = BlockCipher[RSA](params1).get
132 | val rsa2 = BlockCipher[RSA](params2).get
133 | rsa.decryptBlock(rsa.encryptBlock(test).get).get should be(test)
134 | rsa2.decryptBlock(rsa2.encryptBlock(test).get).get should be(test)
135 | }
136 |
137 | it should "conform to the test vector." in {
138 | val n = Seq(0xa8, 0xb3, 0xb2, 0x84, 0xaf, 0x8e, 0xb5, 0x0b, 0x38, 0x70, 0x34, 0xa8, 0x60, 0xf1, 0x46, 0xc4, 0x91, 0x9f, 0x31, 0x87, 0x63, 0xcd, 0x6c, 0x55, 0x98, 0xc8, 0xae, 0x48, 0x11, 0xa1, 0xe0, 0xab, 0xc4, 0xc7, 0xe0, 0xb0, 0x82, 0xd6, 0x93, 0xa5, 0xe7, 0xfc, 0xed, 0x67, 0x5c, 0xf4, 0x66, 0x85, 0x12, 0x77, 0x2c, 0x0c, 0xbc, 0x64, 0xa7, 0x42, 0xc6, 0xc6, 0x30, 0xf5, 0x33, 0xc8, 0xcc, 0x72, 0xf6, 0x2a, 0xe8, 0x33, 0xc4, 0x0b, 0xf2, 0x58, 0x42, 0xe9, 0x84, 0xbb, 0x78, 0xbd, 0xbf, 0x97, 0xc0, 0x10, 0x7d, 0x55, 0xbd, 0xb6, 0x62, 0xf5, 0xc4, 0xe0, 0xfa, 0xb9, 0x84, 0x5c, 0xb5, 0x14, 0x8e, 0xf7, 0x39, 0x2d, 0xd3, 0xaa, 0xff, 0x93, 0xae, 0x1e, 0x6b, 0x66, 0x7b, 0xb3, 0xd4, 0x24, 0x76, 0x16, 0xd4, 0xf5, 0xba, 0x10, 0xd4, 0xcf, 0xd2, 0x26, 0xde, 0x88, 0xd3, 0x9f, 0x16, 0xfb) map { _.toByte }
139 |
140 | val e = Seq(0x01, 0x00, 0x01) map { _.toByte }
141 |
142 | val d = Seq(0x53, 0x33, 0x9c, 0xfd, 0xb7, 0x9f, 0xc8, 0x46, 0x6a, 0x65, 0x5c, 0x73, 0x16, 0xac, 0xa8, 0x5c, 0x55, 0xfd, 0x8f, 0x6d, 0xd8, 0x98, 0xfd, 0xaf, 0x11, 0x95, 0x17, 0xef, 0x4f, 0x52, 0xe8, 0xfd, 0x8e, 0x25, 0x8d, 0xf9, 0x3f, 0xee, 0x18, 0x0f, 0xa0, 0xe4, 0xab, 0x29, 0x69, 0x3c, 0xd8, 0x3b, 0x15, 0x2a, 0x55, 0x3d, 0x4a, 0xc4, 0xd1, 0x81, 0x2b, 0x8b, 0x9f, 0xa5, 0xaf, 0x0e, 0x7f, 0x55, 0xfe, 0x73, 0x04, 0xdf, 0x41, 0x57, 0x09, 0x26, 0xf3, 0x31, 0x1f, 0x15, 0xc4, 0xd6, 0x5a, 0x73, 0x2c, 0x48, 0x31, 0x16, 0xee, 0x3d, 0x3d, 0x2d, 0x0a, 0xf3, 0x54, 0x9a, 0xd9, 0xbf, 0x7c, 0xbf, 0xb7, 0x8a, 0xd8, 0x84, 0xf8, 0x4d, 0x5b, 0xeb, 0x04, 0x72, 0x4d, 0xc7, 0x36, 0x9b, 0x31, 0xde, 0xf3, 0x7d, 0x0c, 0xf5, 0x39, 0xe9, 0xcf, 0xcd, 0xd3, 0xde, 0x65, 0x37, 0x29, 0xea, 0xd5, 0xd1) map { _.toByte }
143 |
144 | val seed = Seq(0x18, 0xb7, 0x76, 0xea, 0x21, 0x06, 0x9d, 0x69, 0x77, 0x6a, 0x33, 0xe9, 0x6b, 0xad, 0x48, 0xe1, 0xdd, 0xa0, 0xa5, 0xef) map { _.toByte }
145 |
146 | val m = Seq(0x66, 0x28, 0x19, 0x4e, 0x12, 0x07, 0x3d, 0xb0, 0x3b, 0xa9, 0x4c, 0xda, 0x9e, 0xf9, 0x53, 0x23, 0x97, 0xd5, 0x0d, 0xba, 0x79, 0xb9, 0x87, 0x00, 0x4a, 0xfe, 0xfe, 0x34) map { _.toByte }
147 |
148 | val c = Seq(0x35, 0x4f, 0xe6, 0x7b, 0x4a, 0x12, 0x6d, 0x5d, 0x35, 0xfe, 0x36, 0xc7, 0x77, 0x79, 0x1a, 0x3f, 0x7b, 0xa1, 0x3d, 0xef, 0x48, 0x4e, 0x2d, 0x39, 0x08, 0xaf, 0xf7, 0x22, 0xfa, 0xd4, 0x68, 0xfb, 0x21, 0x69, 0x6d, 0xe9, 0x5d, 0x0b, 0xe9, 0x11, 0xc2, 0xd3, 0x17, 0x4f, 0x8a, 0xfc, 0xc2, 0x01, 0x03, 0x5f, 0x7b, 0x6d, 0x8e, 0x69, 0x40, 0x2d, 0xe5, 0x45, 0x16, 0x18, 0xc2, 0x1a, 0x53, 0x5f, 0xa9, 0xd7, 0xbf, 0xc5, 0xb8, 0xdd, 0x9f, 0xc2, 0x43, 0xf8, 0xcf, 0x92, 0x7d, 0xb3, 0x13, 0x22, 0xd6, 0xe8, 0x81, 0xea, 0xa9, 0x1a, 0x99, 0x61, 0x70, 0xe6, 0x57, 0xa0, 0x5a, 0x26, 0x64, 0x26, 0xd9, 0x8c, 0x88, 0x00, 0x3f, 0x84, 0x77, 0xc1, 0x22, 0x70, 0x94, 0xa0, 0xd9, 0xfa, 0x1e, 0x8c, 0x40, 0x24, 0x30, 0x9c, 0xe1, 0xec, 0xcc, 0xb5, 0x21, 0x00, 0x35, 0xd4, 0x7a, 0xc7, 0x2e, 0x8a) map { _.toByte }
149 |
150 | val publicKey = (e, n).toKey[RSAKey].get
151 | val privateKey = (e, d, n).toKey[RSAKey].get
152 |
153 | val encryptor = suites.RSAES_OAEP(publicKey, Seq[Byte](), hash.SHA1, { _ ⇒ seed }).get
154 | val decryptor = suites.RSAES_OAEP(privateKey, Seq[Byte](), hash.SHA1, { _ ⇒ seed }).get
155 |
156 | encryptor.encrypt(m).get should be(c)
157 | decryptor.decrypt(c).get should be(m)
158 | }
159 |
160 | "RSASSA_PSS" should "be able to verify a created signature." in {
161 | val signer = khash.RSASSA_PSS()
162 | val message = (1 to 16) map { _.toByte }
163 | val signature = signer(testKey, message).get
164 |
165 | signer.verify(testKey, message, signature).get should be(true)
166 | }
167 |
168 | it should "conform to the test vectors." in {
169 | val n = Seq(
170 | 0xa5, 0x6e, 0x4a, 0x0e, 0x70, 0x10, 0x17, 0x58, 0x9a, 0x51, 0x87, 0xdc, 0x7e, 0xa8, 0x41, 0xd1,
171 | 0x56, 0xf2, 0xec, 0x0e, 0x36, 0xad, 0x52, 0xa4, 0x4d, 0xfe, 0xb1, 0xe6, 0x1f, 0x7a, 0xd9, 0x91,
172 | 0xd8, 0xc5, 0x10, 0x56, 0xff, 0xed, 0xb1, 0x62, 0xb4, 0xc0, 0xf2, 0x83, 0xa1, 0x2a, 0x88, 0xa3,
173 | 0x94, 0xdf, 0xf5, 0x26, 0xab, 0x72, 0x91, 0xcb, 0xb3, 0x07, 0xce, 0xab, 0xfc, 0xe0, 0xb1, 0xdf,
174 | 0xd5, 0xcd, 0x95, 0x08, 0x09, 0x6d, 0x5b, 0x2b, 0x8b, 0x6d, 0xf5, 0xd6, 0x71, 0xef, 0x63, 0x77,
175 | 0xc0, 0x92, 0x1c, 0xb2, 0x3c, 0x27, 0x0a, 0x70, 0xe2, 0x59, 0x8e, 0x6f, 0xf8, 0x9d, 0x19, 0xf1,
176 | 0x05, 0xac, 0xc2, 0xd3, 0xf0, 0xcb, 0x35, 0xf2, 0x92, 0x80, 0xe1, 0x38, 0x6b, 0x6f, 0x64, 0xc4,
177 | 0xef, 0x22, 0xe1, 0xe1, 0xf2, 0x0d, 0x0c, 0xe8, 0xcf, 0xfb, 0x22, 0x49, 0xbd, 0x9a, 0x21, 0x37) map { _.toByte }
178 |
179 | val e = Seq(0x01, 0x00, 0x01) map { _.toByte }
180 |
181 | val d = Seq(
182 | 0x33, 0xa5, 0x04, 0x2a, 0x90, 0xb2, 0x7d, 0x4f, 0x54, 0x51, 0xca, 0x9b, 0xbb, 0xd0, 0xb4, 0x47,
183 | 0x71, 0xa1, 0x01, 0xaf, 0x88, 0x43, 0x40, 0xae, 0xf9, 0x88, 0x5f, 0x2a, 0x4b, 0xbe, 0x92, 0xe8,
184 | 0x94, 0xa7, 0x24, 0xac, 0x3c, 0x56, 0x8c, 0x8f, 0x97, 0x85, 0x3a, 0xd0, 0x7c, 0x02, 0x66, 0xc8,
185 | 0xc6, 0xa3, 0xca, 0x09, 0x29, 0xf1, 0xe8, 0xf1, 0x12, 0x31, 0x88, 0x44, 0x29, 0xfc, 0x4d, 0x9a,
186 | 0xe5, 0x5f, 0xee, 0x89, 0x6a, 0x10, 0xce, 0x70, 0x7c, 0x3e, 0xd7, 0xe7, 0x34, 0xe4, 0x47, 0x27,
187 | 0xa3, 0x95, 0x74, 0x50, 0x1a, 0x53, 0x26, 0x83, 0x10, 0x9c, 0x2a, 0xba, 0xca, 0xba, 0x28, 0x3c,
188 | 0x31, 0xb4, 0xbd, 0x2f, 0x53, 0xc3, 0xee, 0x37, 0xe3, 0x52, 0xce, 0xe3, 0x4f, 0x9e, 0x50, 0x3b,
189 | 0xd8, 0x0c, 0x06, 0x22, 0xad, 0x79, 0xc6, 0xdc, 0xee, 0x88, 0x35, 0x47, 0xc6, 0xa3, 0xb3, 0x25) map { _.toByte }
190 |
191 | val m = Seq(
192 | 0xcd, 0xc8, 0x7d, 0xa2, 0x23, 0xd7, 0x86, 0xdf, 0x3b, 0x45, 0xe0, 0xbb, 0xbc, 0x72, 0x13, 0x26,
193 | 0xd1, 0xee, 0x2a, 0xf8, 0x06, 0xcc, 0x31, 0x54, 0x75, 0xcc, 0x6f, 0x0d, 0x9c, 0x66, 0xe1, 0xb6,
194 | 0x23, 0x71, 0xd4, 0x5c, 0xe2, 0x39, 0x2e, 0x1a, 0xc9, 0x28, 0x44, 0xc3, 0x10, 0x10, 0x2f, 0x15,
195 | 0x6a, 0x0d, 0x8d, 0x52, 0xc1, 0xf4, 0xc4, 0x0b, 0xa3, 0xaa, 0x65, 0x09, 0x57, 0x86, 0xcb, 0x76,
196 | 0x97, 0x57, 0xa6, 0x56, 0x3b, 0xa9, 0x58, 0xfe, 0xd0, 0xbc, 0xc9, 0x84, 0xe8, 0xb5, 0x17, 0xa3,
197 | 0xd5, 0xf5, 0x15, 0xb2, 0x3b, 0x8a, 0x41, 0xe7, 0x4a, 0xa8, 0x67, 0x69, 0x3f, 0x90, 0xdf, 0xb0,
198 | 0x61, 0xa6, 0xe8, 0x6d, 0xfa, 0xae, 0xe6, 0x44, 0x72, 0xc0, 0x0e, 0x5f, 0x20, 0x94, 0x57, 0x29,
199 | 0xcb, 0xeb, 0xe7, 0x7f, 0x06, 0xce, 0x78, 0xe0, 0x8f, 0x40, 0x98, 0xfb, 0xa4, 0x1f, 0x9d, 0x61,
200 | 0x93, 0xc0, 0x31, 0x7e, 0x8b, 0x60, 0xd4, 0xb6, 0x08, 0x4a, 0xcb, 0x42, 0xd2, 0x9e, 0x38, 0x08,
201 | 0xa3, 0xbc, 0x37, 0x2d, 0x85, 0xe3, 0x31, 0x17, 0x0f, 0xcb, 0xf7, 0xcc, 0x72, 0xd0, 0xb7, 0x1c,
202 | 0x29, 0x66, 0x48, 0xb3, 0xa4, 0xd1, 0x0f, 0x41, 0x62, 0x95, 0xd0, 0x80, 0x7a, 0xa6, 0x25, 0xca,
203 | 0xb2, 0x74, 0x4f, 0xd9, 0xea, 0x8f, 0xd2, 0x23, 0xc4, 0x25, 0x37, 0x02, 0x98, 0x28, 0xbd, 0x16,
204 | 0xbe, 0x02, 0x54, 0x6f, 0x13, 0x0f, 0xd2, 0xe3, 0x3b, 0x93, 0x6d, 0x26, 0x76, 0xe0, 0x8a, 0xed,
205 | 0x1b, 0x73, 0x31, 0x8b, 0x75, 0x0a, 0x01, 0x67, 0xd0) map { _.toByte }
206 |
207 | val salt = Seq(
208 | 0xde, 0xe9, 0x59, 0xc7, 0xe0, 0x64, 0x11, 0x36, 0x14, 0x20, 0xff, 0x80, 0x18, 0x5e, 0xd5, 0x7f,
209 | 0x3e, 0x67, 0x76, 0xaf) map { _.toByte }
210 |
211 | val signature = Seq(
212 | 0x90, 0x74, 0x30, 0x8f, 0xb5, 0x98, 0xe9, 0x70, 0x1b, 0x22, 0x94, 0x38, 0x8e, 0x52, 0xf9, 0x71,
213 | 0xfa, 0xac, 0x2b, 0x60, 0xa5, 0x14, 0x5a, 0xf1, 0x85, 0xdf, 0x52, 0x87, 0xb5, 0xed, 0x28, 0x87,
214 | 0xe5, 0x7c, 0xe7, 0xfd, 0x44, 0xdc, 0x86, 0x34, 0xe4, 0x07, 0xc8, 0xe0, 0xe4, 0x36, 0x0b, 0xc2,
215 | 0x26, 0xf3, 0xec, 0x22, 0x7f, 0x9d, 0x9e, 0x54, 0x63, 0x8e, 0x8d, 0x31, 0xf5, 0x05, 0x12, 0x15,
216 | 0xdf, 0x6e, 0xbb, 0x9c, 0x2f, 0x95, 0x79, 0xaa, 0x77, 0x59, 0x8a, 0x38, 0xf9, 0x14, 0xb5, 0xb9,
217 | 0xc1, 0xbd, 0x83, 0xc4, 0xe2, 0xf9, 0xf3, 0x82, 0xa0, 0xd0, 0xaa, 0x35, 0x42, 0xff, 0xee, 0x65,
218 | 0x98, 0x4a, 0x60, 0x1b, 0xc6, 0x9e, 0xb2, 0x8d, 0xeb, 0x27, 0xdc, 0xa1, 0x2c, 0x82, 0xc2, 0xd4,
219 | 0xc3, 0xf6, 0x6c, 0xd5, 0x00, 0xf1, 0xff, 0x2b, 0x99, 0x4d, 0x8a, 0x4e, 0x30, 0xcb, 0xb3, 0x3c) map { _.toByte }
220 |
221 | val key = (e, d, n).toKey[RSAKey].get
222 |
223 | val signer = khash.RSASSA_PSS(hash.SHA1, salt.length, _ ⇒ salt)
224 | signer(key, m).get should be(signature)
225 | signer.verify(key, m, signature).get should be(true)
226 | }
227 | }
228 |
229 |
--------------------------------------------------------------------------------
/src/test/scala/RichBigIntSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | class RichBigIntSpec extends FlatSpec with Matchers {
21 |
22 | "RichBigInt" should "be convertible to a Seq[Byte]." in {
23 | val tests = Seq[(Int, Seq[Int])](
24 | (
25 | 0,
26 | Seq(0, 0, 0)), (
27 | 1,
28 | Seq(0, 0, 1)), (
29 | 255,
30 | Seq(0, 0, 255)), (
31 | 256,
32 | Seq(0, 1, 0)), (
33 | 257,
34 | Seq(0, 1, 1)), (
35 | 511,
36 | Seq(0, 1, 255)))
37 |
38 | for (test <- tests) {
39 | val int = BigInt(test._1)
40 | val bytes = test._2 map { _.toByte }
41 |
42 | int.i2osp(bytes.length).get should be(bytes)
43 | bytes.os2ip should be(int)
44 | }
45 | }
46 |
47 | it should "correctly convert numbers on the upper end of their magnitude." in {
48 | val testBytes = Seq(255, 255, 255) map { _.toByte }
49 | val number = testBytes.os2ip
50 | number.i2osp(3).get should be(testBytes)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/test/scala/SymmetricBlockCipherSuiteSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | class SymmetricBlockCipherSuiteSpec extends FlatSpec with Matchers {
21 | "AES128 with CBC and no padding" should "conform to the test vectors" in {
22 | val testvectors: Seq[(Seq[Byte], Seq[Byte], Seq[Byte])] = Seq(
23 | (
24 | Seq(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F) map { _.toByte },
25 | Seq(0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a) map { _.toByte },
26 | Seq(0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d) map { _.toByte }), (
27 | Seq(0x76, 0x49, 0xAB, 0xAC, 0x81, 0x19, 0xB2, 0x46, 0xCE, 0xE9, 0x8E, 0x9B, 0x12, 0xE9, 0x19, 0x7D) map { _.toByte },
28 | Seq(0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51) map { _.toByte },
29 | Seq(0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2) map { _.toByte }), (
30 | Seq(0x50, 0x86, 0xCB, 0x9B, 0x50, 0x72, 0x19, 0xEE, 0x95, 0xDB, 0x11, 0x3A, 0x91, 0x76, 0x78, 0xB2) map { _.toByte },
31 | Seq(0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef) map { _.toByte },
32 | Seq(0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16) map { _.toByte }), (
33 | Seq(0x73, 0xBE, 0xD6, 0xB8, 0xE3, 0xC1, 0x74, 0x3B, 0x71, 0x16, 0xE6, 0x9E, 0x22, 0x22, 0x95, 0x16) map { _.toByte },
34 | Seq(0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10) map { _.toByte },
35 | Seq(0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7) map { _.toByte }))
36 |
37 | for (test <- testvectors) {
38 | val enc = suites.AES128_CBC_NoPadding(Seq(0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c).map({ _.toByte }).toKey[SymmetricKey128].get, Some(test._1)).get
39 |
40 | enc.encrypt(Iterator(test._2)).toSeq.map({ _.get }).flatten should be(test._3)
41 | enc.decrypt(Iterator(test._3)).toSeq.map({ _.get }).flatten should be(test._2)
42 |
43 | enc.encrypt(test._2).get should be(test._3)
44 | enc.decrypt(test._3).get should be(test._2)
45 | }
46 | }
47 |
48 | "AES with CBC and PKCS7Padding" should "operate on arbitrary iterators" in {
49 |
50 | val tests = Seq[Seq[Seq[Byte]]](
51 | Seq(
52 | Seq(1, 2, 3), Seq(2, 3, 4)), Seq(
53 | Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
54 | Seq(18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30),
55 | Seq(31, 32, 33, 34, 35)))
56 |
57 | for (test <- tests) {
58 | val key = Key.generate[SymmetricKey128]
59 | val enc = suites.AES128_CBC_PKCS7Padding(key).get
60 |
61 | val crypt = enc.encrypt(test.toIterator).toSeq.map({ _.get }).flatten
62 | enc.decrypt(Iterator(crypt)).toSeq.map({ _.get }).flatten should be(test.flatten)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/scala/SymmetricKeySpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 |
20 | class KeySpec extends FlatSpec with Matchers {
21 |
22 | "A SymmetricKey128" should "always have length 16." in {
23 | Seq(1, 2, 3).map({ _.toByte }).toKey[SymmetricKey128] shouldBe a[Failure[_]]
24 | ((1 to 16) map { _.toByte }).toKey[SymmetricKey128] shouldBe a[Success[_]]
25 | }
26 |
27 | "A SymmetricKey192" should "always have length 24." in {
28 | Seq(1, 2, 3).map({ _.toByte }).toKey[SymmetricKey192] shouldBe a[Failure[_]]
29 | ((1 to 24) map { _.toByte }).toKey[SymmetricKey192] shouldBe a[Success[_]]
30 | }
31 |
32 | "A SymmetricKey256" should "always have length 32." in {
33 | Seq(1, 2, 3).map({ _.toByte }).toKey[SymmetricKey256] shouldBe a[Failure[_]]
34 | ((1 to 32) map { _.toByte }).toKey[SymmetricKey256] shouldBe a[Success[_]]
35 | }
36 |
37 | "A SymmetricKey512" should "always have length 64." in {
38 | Seq(1, 2, 3).map({ _.toByte }).toKey[SymmetricKey512] shouldBe a[Failure[_]]
39 | ((1 to 64) map { _.toByte }).toKey[SymmetricKey512] shouldBe a[Success[_]]
40 | }
41 |
42 | "A SymmetricKey1024" should "always have length 128." in {
43 | Seq(1, 2, 3).map({ _.toByte }).toKey[SymmetricKey1024] shouldBe a[Failure[_]]
44 | ((1 to 128) map { _.toByte }).toKey[SymmetricKey1024] shouldBe a[Success[_]]
45 | }
46 |
47 | "MightBuildKey" should "be contravariant in FromType." in {
48 | // After the map 0 until 16 is a IndexedSeq[Byte] and therefore a subclass of Seq[Byte]
49 | // Contravariance makes sure that MightBuildKey[Seq[Byte], SymmetricKey128] is
50 | // considered a subclass of MightBuildKey[IndexedSeq[Byte], SymmetricKey128].
51 | (0 until 16).map({ _.toByte }).toKey[SymmetricKey128]()(SymmetricKey128.mightBuildKey) shouldBe a[Success[_]]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/scala/ThreefishSpec.scala:
--------------------------------------------------------------------------------
1 | /* Copyright 2014, 2015 Richard Wiedenhöft
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | package xyz.wiedenhoeft.scalacrypt
16 |
17 | import org.scalatest._
18 | import scala.util.{ Try, Success, Failure }
19 | import blockciphers._
20 |
21 | class ThreefishSpec extends FlatSpec with Matchers {
22 | val params = Parameters(
23 | 'symmetricKey256 -> Key.generate[SymmetricKey256],
24 | 'symmetricKey512 -> Key.generate[SymmetricKey512],
25 | 'symmetricKey1024 -> Key.generate[SymmetricKey1024],
26 | 'tweak -> ((1 to 16) map { _.toByte }))
27 |
28 | val tf256 = BlockCipher[Threefish256](params).get
29 | val tf512 = BlockCipher[Threefish512](params).get
30 | val tf1024 = BlockCipher[Threefish1024](params).get
31 |
32 | // Key, Tweak, Input, Output
33 | val tests256 = Seq[(Seq[Long], Seq[Long], Seq[Long], Seq[Long])](
34 | (
35 | Seq(0, 0, 0, 0),
36 | Seq(0, 0),
37 | Seq(0, 0, 0, 0),
38 | Seq(0x94EEEA8B1F2ADA84L, 0xADF103313EAE6670L, 0x952419A1F4B16D53L, 0xD83F13E63C9F6B11L)), (
39 | Seq(0x1716151413121110L, 0x1F1E1D1C1B1A1918L, 0x2726252423222120L, 0x2F2E2D2C2B2A2928L),
40 | Seq(0x0706050403020100L, 0x0F0E0D0C0B0A0908L),
41 | Seq(0xF8F9FAFBFCFDFEFFL, 0xF0F1F2F3F4F5F6F7L, 0xE8E9EAEBECEDEEEFL, 0xE0E1E2E3E4E5E6E7L),
42 | Seq(0x277610F5036C2E1FL, 0x25FB2ADD1267773EL, 0x9E1D67B3E4B06872L, 0x3F76BC7651B39682L)))
43 |
44 | val tests512 = Seq[(Seq[Long], Seq[Long], Seq[Long], Seq[Long])](
45 | (
46 | Seq(0, 0, 0, 0, 0, 0, 0, 0),
47 | Seq(0, 0),
48 | Seq(0, 0, 0, 0, 0, 0, 0, 0),
49 | Seq(0xBC2560EFC6BBA2B1L, 0xE3361F162238EB40L, 0xFB8631EE0ABBD175L, 0x7B9479D4C5479ED1L,
50 | 0xCFF0356E58F8C27BL, 0xB1B7B08430F0E7F7L, 0xE9A380A56139ABF1L, 0xBE7B6D4AA11EB47EL)), (
51 | Seq(0x1716151413121110L, 0x1F1E1D1C1B1A1918L, 0x2726252423222120L, 0x2F2E2D2C2B2A2928L,
52 | 0x3736353433323130L, 0x3F3E3D3C3B3A3938L, 0x4746454443424140L, 0x4F4E4D4C4B4A4948L),
53 | Seq(0x0706050403020100L, 0x0F0E0D0C0B0A0908L),
54 | Seq(0xF8F9FAFBFCFDFEFFL, 0xF0F1F2F3F4F5F6F7L, 0xE8E9EAEBECEDEEEFL, 0xE0E1E2E3E4E5E6E7L,
55 | 0xD8D9DADBDCDDDEDFL, 0xD0D1D2D3D4D5D6D7L, 0xC8C9CACBCCCDCECFL, 0xC0C1C2C3C4C5C6C7L),
56 | Seq(0xD4A32EDD6ABEFA1CL, 0x6AD5C4252C3FF743L, 0x35AC875BE2DED68CL, 0x99A6C774EA5CD06CL,
57 | 0xDCEC9C4251D7F4F8L, 0xF5761BCB3EF592AFL, 0xFCABCB6A3212DF60L, 0xFD6EDE9FF9A2E14EL)))
58 |
59 | val tests1024 = Seq[(Seq[Long], Seq[Long], Seq[Long], Seq[Long])](
60 | (
61 | Seq(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
62 | Seq(0, 0),
63 | Seq(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
64 | Seq(0x04B3053D0A3D5CF0L, 0x0136E0D1C7DD85F7L, 0x067B212F6EA78A5CL, 0x0DA9C10B4C54E1C6L,
65 | 0x0F4EC27394CBACF0L, 0x32437F0568EA4FD5L, 0xCFF56D1D7654B49CL, 0xA2D5FB14369B2E7BL,
66 | 0x540306B460472E0BL, 0x71C18254BCEA820DL, 0xC36B4068BEAF32C8L, 0xFA4329597A360095L,
67 | 0xC4A36C28434A5B9AL, 0xD54331444B1046CFL, 0xDF11834830B2A460L, 0x1E39E8DFE1F7EE4FL)), (
68 | Seq(0x1716151413121110L, 0x1F1E1D1C1B1A1918L, 0x2726252423222120L, 0x2F2E2D2C2B2A2928L,
69 | 0x3736353433323130L, 0x3F3E3D3C3B3A3938L, 0x4746454443424140L, 0x4F4E4D4C4B4A4948L,
70 | 0x5756555453525150L, 0x5F5E5D5C5B5A5958L, 0x6766656463626160L, 0x6F6E6D6C6B6A6968L,
71 | 0x7776757473727170L, 0x7F7E7D7C7B7A7978L, 0x8786858483828180L, 0x8F8E8D8C8B8A8988L),
72 | Seq(0x0706050403020100L, 0x0F0E0D0C0B0A0908L),
73 | Seq(0xF8F9FAFBFCFDFEFFL, 0xF0F1F2F3F4F5F6F7L, 0xE8E9EAEBECEDEEEFL, 0xE0E1E2E3E4E5E6E7L,
74 | 0xD8D9DADBDCDDDEDFL, 0xD0D1D2D3D4D5D6D7L, 0xC8C9CACBCCCDCECFL, 0xC0C1C2C3C4C5C6C7L,
75 | 0xB8B9BABBBCBDBEBFL, 0xB0B1B2B3B4B5B6B7L, 0xA8A9AAABACADAEAFL, 0xA0A1A2A3A4A5A6A7L,
76 | 0x98999A9B9C9D9E9FL, 0x9091929394959697L, 0x88898A8B8C8D8E8FL, 0x8081828384858687L),
77 | Seq(0x483AC62C27B09B59L, 0x4CB85AA9E48221AAL, 0x80BC1644069F7D0BL, 0xFCB26748FF92B235L,
78 | 0xE83D70243B5D294BL, 0x316A3CA3587A0E02L, 0x5461FD7C8EF6C1B9L, 0x7DD5C1A4C98CA574L,
79 | 0xFDA694875AA31A35L, 0x03D1319C26C2624CL, 0xA2066D0DF2BF7827L, 0x6831CCDAA5C8A370L,
80 | 0x2B8FCD9189698DACL, 0xE47818BBFD604399L, 0xDF47E519CBCEA541L, 0x5EFD5FF4A5D4C259L)))
81 |
82 | val test0Params = Parameters(
83 | 'symmetricKey256 -> Threefish.words2block(tests256(0)._1).toKey[SymmetricKey256].get,
84 | 'symmetricKey512 -> Threefish.words2block(tests512(0)._1).toKey[SymmetricKey512].get,
85 | 'symmetricKey1024 -> Threefish.words2block(tests1024(0)._1).toKey[SymmetricKey1024].get,
86 | 'tweak -> Threefish.words2block(tests256(0)._2))
87 |
88 | val test1Params = Parameters(
89 | 'symmetricKey256 -> Threefish.words2block(tests256(1)._1).toKey[SymmetricKey256].get,
90 | 'symmetricKey512 -> Threefish.words2block(tests512(1)._1).toKey[SymmetricKey512].get,
91 | 'symmetricKey1024 -> Threefish.words2block(tests1024(1)._1).toKey[SymmetricKey1024].get,
92 | 'tweak -> Threefish.words2block(tests256(1)._2))
93 |
94 | "The Threefish mix function" should "be reversible." in {
95 | val tests = Seq[(Long, Long, Int)](
96 | (5122421, 2141242, 53),
97 | (12, Long.MaxValue, 5),
98 | (Long.MaxValue, 5152124, 5),
99 | (Long.MaxValue, Long.MaxValue, 34))
100 | for (test <- tests) {
101 | val mix = Threefish.mix(test._1, test._2, test._3)
102 | val unmix = Threefish.unmix(mix(0), mix(1), test._3)
103 | unmix(0) should be(test._1)
104 | unmix(1) should be(test._2)
105 | }
106 | }
107 |
108 | it should "be consistent with the testvectors." in {
109 | val tests = Seq[(Seq[Byte], Seq[Byte], Int, Seq[Byte], Seq[Byte])](
110 | (
111 | Seq(0, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
112 | Seq(0, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
113 | 0,
114 | Seq(0, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
115 | Seq(0, 0, 0, 0, 0, 0, 0, 0) map { _.toByte }), (
116 | Seq(1, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
117 | Seq(1, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
118 | 0,
119 | Seq(2, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
120 | Seq(3, 0, 0, 0, 0, 0, 0, 0) map { _.toByte }), (
121 | Seq(1, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
122 | Seq(1, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
123 | 1,
124 | Seq(2, 0, 0, 0, 0, 0, 0, 0) map { _.toByte },
125 | Seq(0, 0, 0, 0, 0, 0, 0, 0) map { _.toByte }), (
126 | Seq(232, 3, 0, 0, 0, 0, 0, 0) map { _.toByte },
127 | Seq(208, 7, 0, 0, 0, 0, 0, 0) map { _.toByte },
128 | 5,
129 | Seq(184, 11, 0, 0, 0, 0, 0, 0) map { _.toByte },
130 | Seq(184, 241, 0, 0, 0, 0, 0, 0) map { _.toByte }), (
131 | Seq(232, 3, 0, 0, 0, 0, 0, 0) map { _.toByte },
132 | Seq(208, 7, 0, 0, 0, 0, 0, 0) map { _.toByte },
133 | 62,
134 | Seq(184, 11, 0, 0, 0, 0, 0, 0) map { _.toByte },
135 | Seq(76, 10, 0, 0, 0, 0, 0, 0) map { _.toByte }))
136 |
137 | for (test <- tests) {
138 | val a = Threefish.bytes2word(test._1)
139 | val b = Threefish.bytes2word(test._2)
140 | val r = test._3
141 | val x = Threefish.bytes2word(test._4)
142 | val y = Threefish.bytes2word(test._5)
143 |
144 | Threefish.mix(a, b, r) should be(Seq(x, y))
145 | }
146 | }
147 |
148 | "The Threefish conversion between bytes and words" should "be reversible." in {
149 | Threefish.block2words(Threefish.words2block(tests256(1)._3)) should be(tests256(1)._3)
150 | Threefish.block2words(Threefish.words2block(tests256(1)._4)) should be(tests256(1)._4)
151 | }
152 |
153 | "Threefish256" should "correctly decrypt a previously encrypted block." in {
154 | val test = (1 to 32) map { _.toByte }
155 | tf256.decryptBlock(tf256.encryptBlock(test).get).get should be(test)
156 | }
157 |
158 | it should "have its key and tweak correctly initialized." in {
159 | val tf = BlockCipher[Threefish256](test1Params).get
160 |
161 | tf.keyWords.length should be(5)
162 | Threefish.words2block(tf.keyWords) should be(Seq(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 34, 26, 252, 169, 218, 27, 209, 27) map { _.toByte })
163 | tf.keyWords.slice(0, tf.keyWords.length - 1) should be(tests256(1)._1)
164 |
165 | tf.tweakWords.length should be(3)
166 | Threefish.words2block(tf.tweakWords) should be(Seq(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 8, 8, 8, 8, 8, 8, 8, 8) map { _.toByte })
167 | tf.tweakWords.slice(0, 2) should be(tests256(1)._2)
168 | }
169 |
170 | it should "use the correct round keys." in {
171 | val tf = BlockCipher[Threefish256](test1Params).get
172 |
173 | val k = tf.keyWords
174 | val t = tf.tweakWords
175 | val rk = tf.roundKeys
176 |
177 | rk(0) should be(Seq(k(0), k(1) + t(0), k(2) + t(1), k(3)))
178 | rk(1) should be(Seq(k(1), k(2) + t(1), k(3) + t(2), k(4) + 1))
179 | rk(2) should be(Seq(k(2), k(3) + t(2), k(4) + t(0), k(0) + 2))
180 | rk(3) should be(Seq(k(3), k(4) + t(0), k(0) + t(1), k(1) + 3))
181 | rk(4) should be(Seq(k(4), k(0) + t(1), k(1) + t(2), k(2) + 4))
182 | rk(5) should be(Seq(k(0), k(1) + t(2), k(2) + t(0), k(3) + 5))
183 | rk(6) should be(Seq(k(1), k(2) + t(0), k(3) + t(1), k(4) + 6))
184 | rk(7) should be(Seq(k(2), k(3) + t(1), k(4) + t(2), k(0) + 7))
185 | rk(8) should be(Seq(k(3), k(4) + t(2), k(0) + t(0), k(1) + 8))
186 | rk(9) should be(Seq(k(4), k(0) + t(0), k(1) + t(1), k(2) + 9))
187 | rk(10) should be(Seq(k(0), k(1) + t(1), k(2) + t(2), k(3) + 10))
188 | rk(11) should be(Seq(k(1), k(2) + t(2), k(3) + t(0), k(4) + 11))
189 | rk(12) should be(Seq(k(2), k(3) + t(0), k(4) + t(1), k(0) + 12))
190 | rk(13) should be(Seq(k(3), k(4) + t(1), k(0) + t(2), k(1) + 13))
191 | rk(14) should be(Seq(k(4), k(0) + t(2), k(1) + t(0), k(2) + 14))
192 | rk(15) should be(Seq(k(0), k(1) + t(0), k(2) + t(1), k(3) + 15))
193 | rk(16) should be(Seq(k(1), k(2) + t(1), k(3) + t(2), k(4) + 16))
194 | rk(17) should be(Seq(k(2), k(3) + t(2), k(4) + t(0), k(0) + 17))
195 | rk(18) should be(Seq(k(3), k(4) + t(0), k(0) + t(1), k(1) + 18))
196 |
197 | Threefish.words2block(rk(0)) should be(Seq(16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 40, 41, 42, 43, 44, 45, 46, 47) map { _.toByte })
198 | Threefish.words2block(rk(1)) should be(Seq(24, 25, 26, 27, 28, 29, 30, 31, 40, 42, 44, 46, 48, 50, 52, 54, 48, 49, 50, 51, 52, 53, 54, 55, 35, 26, 252, 169, 218, 27, 209, 27) map { _.toByte })
199 | Threefish.words2block(rk(2)) should be(Seq(32, 33, 34, 35, 36, 37, 38, 39, 48, 49, 50, 51, 52, 53, 54, 55, 34, 27, 254, 172, 222, 32, 215, 34, 18, 17, 18, 19, 20, 21, 22, 23) map { _.toByte })
200 | Threefish.words2block(rk(3)) should be(Seq(40, 41, 42, 43, 44, 45, 46, 47, 34, 27, 254, 172, 222, 32, 215, 34, 24, 26, 28, 30, 32, 34, 36, 38, 27, 25, 26, 27, 28, 29, 30, 31) map { _.toByte })
201 | Threefish.words2block(rk(4)) should be(Seq(34, 26, 252, 169, 218, 27, 209, 27, 24, 26, 28, 30, 32, 34, 36, 38, 32, 33, 34, 35, 36, 37, 38, 39, 36, 33, 34, 35, 36, 37, 38, 39) map { _.toByte })
202 | Threefish.words2block(rk(5)) should be(Seq(16, 17, 18, 19, 20, 21, 22, 23, 32, 33, 34, 35, 36, 37, 38, 39, 32, 34, 36, 38, 40, 42, 44, 46, 45, 41, 42, 43, 44, 45, 46, 47) map { _.toByte })
203 | Threefish.words2block(rk(6)) should be(Seq(24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 40, 26, 252, 169, 218, 27, 209, 27) map { _.toByte })
204 | Threefish.words2block(rk(7)) should be(Seq(32, 33, 34, 35, 36, 37, 38, 39, 48, 50, 52, 54, 56, 58, 60, 62, 42, 34, 4, 178, 226, 35, 217, 35, 23, 17, 18, 19, 20, 21, 22, 23) map { _.toByte })
205 | Threefish.words2block(rk(8)) should be(Seq(40, 41, 42, 43, 44, 45, 46, 47, 42, 34, 4, 178, 226, 35, 217, 35, 16, 18, 20, 22, 24, 26, 28, 30, 32, 25, 26, 27, 28, 29, 30, 31) map { _.toByte })
206 | Threefish.words2block(rk(9)) should be(Seq(34, 26, 252, 169, 218, 27, 209, 27, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 41, 33, 34, 35, 36, 37, 38, 39) map { _.toByte })
207 | Threefish.words2block(rk(10)) should be(Seq(16, 17, 18, 19, 20, 21, 22, 23, 32, 34, 36, 38, 40, 42, 44, 46, 40, 41, 42, 43, 44, 45, 46, 47, 50, 41, 42, 43, 44, 45, 46, 47) map { _.toByte })
208 | Threefish.words2block(rk(11)) should be(Seq(24, 25, 26, 27, 28, 29, 30, 31, 40, 41, 42, 43, 44, 45, 46, 47, 40, 42, 44, 46, 48, 50, 52, 54, 45, 26, 252, 169, 218, 27, 209, 27) map { _.toByte })
209 | Threefish.words2block(rk(12)) should be(Seq(32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46, 48, 50, 52, 54, 42, 35, 6, 181, 230, 40, 223, 42, 28, 17, 18, 19, 20, 21, 22, 23) map { _.toByte })
210 | Threefish.words2block(rk(13)) should be(Seq(40, 41, 42, 43, 44, 45, 46, 47, 42, 35, 6, 181, 230, 40, 223, 42, 24, 25, 26, 27, 28, 29, 30, 31, 37, 25, 26, 27, 28, 29, 30, 31) map { _.toByte })
211 | Threefish.words2block(rk(14)) should be(Seq(34, 26, 252, 169, 218, 27, 209, 27, 24, 25, 26, 27, 28, 29, 30, 31, 24, 26, 28, 30, 32, 34, 36, 38, 46, 33, 34, 35, 36, 37, 38, 39) map { _.toByte })
212 | Threefish.words2block(rk(15)) should be(Seq(16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 55, 41, 42, 43, 44, 45, 46, 47) map { _.toByte })
213 | Threefish.words2block(rk(16)) should be(Seq(24, 25, 26, 27, 28, 29, 30, 31, 40, 42, 44, 46, 48, 50, 52, 54, 48, 49, 50, 51, 52, 53, 54, 55, 50, 26, 252, 169, 218, 27, 209, 27) map { _.toByte })
214 | Threefish.words2block(rk(17)) should be(Seq(32, 33, 34, 35, 36, 37, 38, 39, 48, 49, 50, 51, 52, 53, 54, 55, 34, 27, 254, 172, 222, 32, 215, 34, 33, 17, 18, 19, 20, 21, 22, 23) map { _.toByte })
215 | Threefish.words2block(rk(18)) should be(Seq(40, 41, 42, 43, 44, 45, 46, 47, 34, 27, 254, 172, 222, 32, 215, 34, 24, 26, 28, 30, 32, 34, 36, 38, 42, 25, 26, 27, 28, 29, 30, 31) map { _.toByte })
216 | }
217 |
218 | it should "use the correct additional key and tweak word." in {
219 | val tf = BlockCipher[Threefish256](test1Params).get
220 |
221 | tf.keyWords.last should be(2004413935125273122L)
222 | tf.tweakWords.last should be(578721382704613384L)
223 | }
224 |
225 | it should "conform to the testvectors." in {
226 | val tf0 = BlockCipher[Threefish256](test0Params).get
227 | val c0 = Threefish.words2block(tests256(0)._4.zip(tests256(0)._3) map { t ⇒ t._1 ^ t._2 })
228 | tf0.encryptBlock(Threefish.words2block(tests256(0)._3)).get should be(c0)
229 |
230 | val tf1 = BlockCipher[Threefish256](test1Params).get
231 | val c1 = Threefish.words2block(tests256(1)._4.zip(tests256(1)._3) map { t ⇒ t._1 ^ t._2 })
232 | tf1.encryptBlock(Threefish.words2block(tests256(1)._3)).get should be(c1)
233 | }
234 |
235 | def testPermutation(a: Seq[Int], b: Seq[Int]) = {
236 | for (i <- (0 until a.length)) {
237 | a(b(i)) should be(i)
238 | }
239 | }
240 |
241 | it should "have the correct reverse permutation" in {
242 | val tf = BlockCipher[Threefish256](test1Params).get
243 | testPermutation(tf.permutation, tf.reversePermutation)
244 | }
245 |
246 | "Threefish512" should "correctly decrypt a previously encrypted block." in {
247 | val test = (1 to 64) map { _.toByte }
248 | tf512.decryptBlock(tf512.encryptBlock(test).get).get should be(test)
249 | }
250 |
251 | it should "conform to the testvectors." in {
252 | val tf0 = BlockCipher[Threefish512](test0Params).get
253 | val c0 = Threefish.words2block(tests512(0)._4.zip(tests512(0)._3) map { t ⇒ t._1 ^ t._2 })
254 | tf0.encryptBlock(Threefish.words2block(tests512(0)._3)).get should be(c0)
255 |
256 | val tf1 = BlockCipher[Threefish512](test1Params).get
257 | val c1 = Threefish.words2block(tests512(1)._4.zip(tests512(1)._3) map { t ⇒ t._1 ^ t._2 })
258 | tf1.encryptBlock(Threefish.words2block(tests512(1)._3)).get should be(c1)
259 | }
260 |
261 | it should "have the correct reverse permutation" in {
262 | val tf = BlockCipher[Threefish512](test1Params).get
263 | testPermutation(tf.permutation, tf.reversePermutation)
264 | }
265 |
266 | "Threefish1024" should "correctly decrypt a previously encrypted block." in {
267 | val test = (1 to 128) map { _.toByte }
268 | tf1024.decryptBlock(tf1024.encryptBlock(test).get).get should be(test)
269 | }
270 |
271 | it should "conform to the testvectors." in {
272 | val tf0 = BlockCipher[Threefish1024](test0Params).get
273 | val c0 = Threefish.words2block(tests1024(0)._4.zip(tests1024(0)._3) map { t ⇒ t._1 ^ t._2 })
274 | tf0.encryptBlock(Threefish.words2block(tests1024(0)._3)).get should be(c0)
275 |
276 | val tf1 = BlockCipher[Threefish1024](test1Params).get
277 | val c1 = Threefish.words2block(tests1024(1)._4.zip(tests1024(1)._3) map { t ⇒ t._1 ^ t._2 })
278 | tf1.encryptBlock(Threefish.words2block(tests1024(1)._3)).get should be(c1)
279 | }
280 |
281 | it should "have the correct reverse permutation" in {
282 | val tf = BlockCipher[Threefish256](test1Params).get
283 | testPermutation(tf.permutation, tf.reversePermutation)
284 | }
285 |
286 | "Threefish_CBC_256" should "decrypt a previously encrypted message." in {
287 | val message = (0 until 255) map { _.toByte }
288 | val suite = suites.Threefish256_CBC_PKCS7Padding(Key.generate[SymmetricKey256]).get
289 | val cipher = suite.encrypt(message).get
290 |
291 | suite.decrypt(cipher).get should be(message)
292 | }
293 |
294 | "Threefish_CBC_512" should "decrypt a previously encrypted message." in {
295 | val message = (0 until 255) map { _.toByte }
296 | val suite = suites.Threefish512_CBC_PKCS7Padding(Key.generate[SymmetricKey512]).get
297 | val cipher = suite.encrypt(message).get
298 |
299 | suite.decrypt(cipher).get should be(message)
300 | }
301 |
302 | "Threefish_CBC_1024" should "decrypt a previously encrypted message." in {
303 | val message = (0 until 255) map { _.toByte }
304 | val suite = suites.Threefish1024_CBC_PKCS7Padding(Key.generate[SymmetricKey1024]).get
305 | val cipher = suite.encrypt(message).get
306 |
307 | suite.decrypt(cipher).get should be(message)
308 | }
309 | }
310 |
--------------------------------------------------------------------------------