├── .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 | [![Build Status](https://travis-ci.org/Richard-W/scalacrypt.svg?branch=master)](https://travis-ci.org/Richard-W/scalacrypt) 5 | [![Coverage Status](https://coveralls.io/repos/Richard-W/scalacrypt/badge.svg)](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 | """ |AAAAAgEAmMaSAJ7if8+Sn3InOf+6SKrOg0ilzRp8QYY60CFbIGNRKYg5MkMQAyaqJr9zAFW9xeu9 263 | |9DTyzqr9FwBUaJurJNRQvIMxsK/M01bWXbmFJWNsUce6g9icCJWHzqwE69iMj1HkU/0bhONefc/a 264 | |/82siEJkVttIOu4cSmNKOvuuQHFaBQ9VzP9jtZZqegomlS4j/Ib1XQjPZTPkH2gOqUjWR9rWFAhk 265 | |mtyjdxQQLdXoHpmJzAYvmaZnnMoxQuZWSwVhlg+pBxWn2aMtDchuM+y19RDeS6HL6EO7dqm9f36m 266 | |bzEtoOW8Jt+1YEWRW8AL+bz3gZ/XLgWVBCWnKdnrz5DMHmaaIrXtoEvgVn7g6079bBPKSrKYyGlQ 267 | |ubEH68w9HcP34tz7SFM1SzhrUm5S+bCQN8acX8qqcXJ74sj0RNKznTO12eM/HW2KHSQN3BNaY2Px 268 | |sVRXhkgWootjs4dxjNKkYvsXq3etPycfVyLAOPmfal9r9d1XK60+opfRPXamfjHQOvhxALRlwfud 269 | |zI8e6OkHVm0/EswwM+3vUuAkjFKwy61SKaw23fUm6Rwb/BTZjmydmPkItwr3PWHsSCUmjvEoSuW5 270 | |+35GWuN8XN5enY9RauALADOFy5Dk6wCzWdh3qybrNW8kbuLR3ArSquIztsnT/jcYw288I7+d873n 271 | |FBuE3lUBAAAAAwEAAQIAAAIAGLZeHZ2V08jW1dXYRIh6MJD4kMHql+/F06+LyejrXaTTFx3C6r9w 272 | |UqIpedUUHCTCasaEVoFOGWINSHA0NyufFnkFikjKe+MkBbeRO13sDK01c1EUeYlLTBQsAKFQtnmz 273 | |2ucLQQ67KdbBjSZXLXOuief7ZRVZbLbheqLu+fWGLURopFLjtSJGlbP8CzujHBR3m7yU6fSn353y 274 | |M6ZYYMe4aa0bXegxpd80zek/6LomLvT1FjyV7Iu/TNxj9YdexAndzDFCTTQSj6DWg9k9Akcy865D 275 | |1wYX/r0eEMbKMVpiP7A7yj//HGapZyY5qha5mS8Y9i3N19LtVNtmW921SEEK00wJOrgWyKLb8hNP 276 | |Tz7O7bhSZmIO9pcP8v+03Pg1+nBVoKzWJQri6fI4InUmg3VrCi6rGecexFMxnuqZgK5gNZJzbD4N 277 | |JmN2KGNyc4irLhrheJ5yK5jJIaOc97nRd12C3GLKEp4J6h+BgdrLUDy/q3cMTfsdHMzMmqrTdR79 278 | |AAidnrzl7hMgXixG2OY1RXgXV0sbWIgpQcBsIKHuKEd6UTMNuDI6eRKD1uIPfxhNfETSu3Jn/9gj 279 | |m2uVUmtMLtO55p0gcX32YQMQj5ettmx1pVzc8uMtiHA+aahXD2ogozQaUuv9iWkMAWPsB7aHzuko 280 | |pJdC57SbzakHb5WVzan1DqEDAAABAQDOhQsgHllkO+sUc2hAYFeZ17vFI75LJXft6Zmbp5hDRlkX 281 | |rZaLSAdwabcGFnBxjL5ggMIyKO98uJsc78BGCRWmDqxXDv91478gvhxEa/HBt2gNl3tOMySq1Ami 282 | |MDI+t3J+BNh787o1OTqG7OghTaiYzczMFe0JwKa6hq4wKK0S4RrASn+17QU1T4Wyhhu136GQ2R2r 283 | |RVF1/5BtaUPVfzv2S+uceYKw+EY2klFDkzcp7S6UiQDikjtCmnw65NFj+IBDcyAv5wUUX5NYaI89 284 | |UCai6n822HbJNN+vcZa85wvsLmTRTLTWz5ltkncQe6AAru1ljzkqeSIIeLcKCYNPQPbdBAAAAQEA 285 | |vWEeN+hTZflaHn69nWBVVWhc1QjWfMU6MBSfAUECdulkz+KMvHUOh76mjpXSu3CKC1mwIvVKLXNx 286 | |GxE2k40X8kfYrYDumNppRpf0R81p+iOZPA6xrOjFHzZajFXT1C3/lwWznIq9TDlLkhC5n+LccVq8 287 | |lNziiardbT0Jpmg0CwURhkFVbbCP17E72Txw0op3GhtYzVHnqw/snD+KU712X8DI6uWUt1+Zpm2a 288 | |Tf0Vi2aWK7bu+evwAZlGyuvjkE71kc5XAox4O/wlf/DWXRyVPskj17IASgu3BTg0h4hIX3qln9YA 289 | |pL01aB0TLkrgykQzChsJX/DbyCr+bDUAfcPB2QUAAAEAUUTt0d/fkaA6rDuWJO9EydepnrSoJ+5A 290 | |ubEZr7VOJ/tBCB5Zhcn8k3ImghDGgwi9ykAhK5gMVmpXMBXw9h6RFF3l2ASg5wWOqxXlDc/kvTSt 291 | |j9uyvF1H6qmyeM66lw+d0JWbk3ugJV21+G62EpT66dbi5tUiCJp1giWJ2o3HPgyzeERY6YCycf4v 292 | |QMehk/rDG7s0/7cxjVvavBOWjCebsxrBRzxR/85T4xnFPPBr3uXlVLJtVLvy8gzVIl/1PoAGCYT+ 293 | |f5tL1m6eD0ZmR9yIt8fL9AtPA3L5K5NpnEDX4kOHjQ3AhGABoqrmi+f6WQp9hV/NQTeV+vt2HE8O 294 | |C1wnSQYAAAEASXPAr7iJmFS1knxf+QljL6Qx1WL/JhetMPbekTLwzMRLmKHrKjFQuG/G1CjiOlc1 295 | |A5/+xCBVa/mJlhEAFQy1jAA311vZrymPiZToZ20RvLZP+c5NNZ52zltblXC4n2RT7PSGLKJXN5hF 296 | |alrYVF4+WCz0VdyydOjzxynUc1mZTejiWis/AjNoJyWT6/cYX2DbPyH6OHCbJWsgv52Zfk9O+Wah 297 | |xxHSs6j9xGJgZf1SfOYGOuBSIldTmJslrRD/C3rEno/kiZWIEOQEe3IjAqxSaq7DGybsG8wdaYXa 298 | |QfMm9vlwAeWUDFFixIX6aYsbUvhOv42q/i5CYInkcn3AOgdSSQcAAAEBAIvTiQ7M1rjk8qHI3FJ5 299 | |nScu8hPIBHPyLy276I/tQhHyMZKt7/pv5sHiz9xm8Tq4j99rPARfj17LrvkEjepk9eVhxzdAuTrO 300 | |VlXXtA7RcsodCljnsnt7No2jHhKWUGjqQTcsvrZWt9vRtwwF2ndzRvEooPdMykP0cF3UQo9l3opo 301 | |+n71/gdqSZ0wY1Os00vSYp0nraw7j2Xp25kmPTUnJs7Ua9LC3dhw7bdSoJrb1FAB8pdJ08Rm2UOw 302 | |3BPQrEUZQlTuPWHcTuw50l/eh3REqhV9mb38E9Q6NtgrM/vMpiCJPJzwpucikEH+dJl11v+rCaMa 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 | """ |AAAAAgBuzVcPVJSUrKe/2mHcuJU50rY8dtbUd1vCc9U2GR1NoUFWUAkQ9sG7dzb/ab7Kd4064l58 54 | |mVpV7LQGi2pOJIUh6v/0OgnH9G8kkVgFAJ8G77w83HbOf6oYeHv/fJsgvHOCXAsVb4q60w8q6peV 55 | |vSf39Xh7NFoEKn0unvud/8rZTuUE9KQuIZE1igyjcfMqqMmkYiz7vJEa0Nctqm0GEU5XfQT6nyZ9 56 | |nHkrJ3w4B+RcM9e6XEzmUbuURM55z1fhXAwoFX05WHuOpbRlJCXds35ObMampdSDClw3D306NBUN 57 | |Q/XSn/8wwmrUTrzy76iHFa2Kx26S/kkgG9b9vw3BAadLV35ZBIaoikbyo0Ce5c35PgPj9p1AKfvx 58 | |cZZx2uS6EB2V77F9+eEp3VeFI9bD8PoPAFH8gcgfkQTqKP95CfMx8LALAgQ7YKQj0jAUe7bIbdYw 59 | |ibKnCs/Qk9TQwKhQeoWBYKhNzjWwamsvugSjtqN0deXsQY4rhOCTm+z49+wI2A1ZrVQkAu2LUqtO 60 | |kgQj3ESqvk+Txrd8BOt8wYii1XhAW/uc8dVn0vNISOgcYE+F19R6fJESmHeskNeXOug9GjXFHjmL 61 | |PcXm+XQPLip7T+8ofRl/g40CbezVDJGQXYLK3FNY7O37bvcBttRkMCHgyGNfwDlxskOqvSxf56t1 62 | |jZsbnQEAAAADAQABAgAAAgBpfJKK5NJDrRBTRZxvn0dqcmHOtLEYO74fJVnyE/zi4Ees3auT++g7 63 | |CmdQyKK8bQ8G5WZhJzpqnVxS8r21QW2B+hHNuMeBFwQNA1aSBKJh3zENnDJ7q+0LOQGMHVwu0VsC 64 | |25AtyzB/ZtqE2CySgTAhEBCw8wlT+AOPtAoGxPSg6Ex+6E26IXj2SMR5gs2namP2XQVIcKCQsAx7 65 | |zEnZQfacskhgNc5WPfbZj9DiIxlUcoLKSH9pspxrrwoHy891nMVyD+tVAhk3mChjNgPayrzP+x0w 66 | |oKXuT5m1TcjB9b2pEG9jI7G3ARDlqUSX4HRIjunWOsHZrb9+kyKeFxsICgPzH64/3i1mf+LflYDJ 67 | |wQvT8dKsAvGdIKFsxOwDann7PmK2FH3f9dPizQ84PJwHofn5o2jl9LZ1+GHYsiXuKBrx/iIRUXzi 68 | |bJC8kRDwmYTFxsyKgtlNJ/orj2O2R8jQGtt6uNu4LG9A7vcRjvfUmNl6A4w+EG+ozTxjkJNfAyhY 69 | |1qTkYyIXctLc+R57X0DXY/l8zrb0bpcN1UddoIekHgAC71HxEs8sv7FPt3EQIURVL3ZdTOkeXhe/ 70 | |0eQ9oz3P86gQtYLUEWKrn3UYBwRDINarTYYq5ogn15DEyJQ8qsP5i1zDBP8syP1fLAvporZNiDwQ 71 | |0DAf5NVOD8MfN7INPnlw3QMAAAEBAIy/AdO+EgePnlwDC7SQSJaN1AfU2SLL5yRCIMZvQlizXjga 72 | |NKE9MxYlDrJaey7iOuUydfcMRp2Yy8cQ/rePn8qs0pbEOpEETuM6yeEDBmvjsC2ISbf44AHR1rD6 73 | |Jbz1p3hbOdX8TG49xQjkVvCHDj9pfNrvO+yZuyuFE+SiaHgBWGLt0FyBbExnNNjUjgdAU2GucIlC 74 | |L6LdwrYM2/oeViX2dz59UqyDkJ+zpllK6y6IOtHFlTQSN9XxZ3khXQqFwY+850LbZecJ8CfulDZt 75 | |YPBtZzWKwxuO9ArIVnbfP2hJdb5WynkcGvkXIUmmieuQVMWuPYVgDWRP0SOyoviWq4MEAAABAQDJ 76 | |iRaQ8OB5gvK08hcv9E/FV0pMwpFSME0bKJGiiFvu8geqce9Ql0qP5X82UI6qXOA1UqbfCn/oXY14 77 | |0dukLZ+5nl7L3gC2/kQ1UJOub6q8rLMRVMkjOxrK0zkaKLmJCFiwgqz5kXbuk2yL21eXO8vR7ZgU 78 | |Xl+0z/+CG0bRzDnKmXTo3m2YBxS4y4GofnBMYwHTQ+h7htlwhSMvbt/M9AQ8MhcWLC+pCyrCkAFg 79 | |iCIIgqfg9hciv1hrsbLQUweXjQnIWYEEc9lKYHkV1R8Sm0NGgSakVQMUA8Aeq9bcs+oe+CgdIaUE 80 | |kWDqSy+t32P7W0zOe0zWu6XDG85OQlpnANJfBQAAAQBCx3HffGRjkAIMGCnan0bBoEOE/7mSp/pg 81 | |mrugSzPIkDpZFh34jugJTsXdW87snMxi6QFNmVZ7+f5d0jN49r1TVZKJlEtReSDQ8ZjNmTjXwe1a 82 | |fTq0/nGSi3R4/gcf09KE6YvUeuPsvoQZRvX1I69f6PYjjrT4+qvy5y5cIa69ma3NKpbc/U9cJO/P 83 | |HXLR//RP+YtNpMijVuLGq+1HjFNFqd5EGQQ79CSPkZaQ8VoK9vopg7zaOvahHOwCbhEOKA6B25hC 84 | |gYSlDXMUDz/lXxpKmS4KGm+eSMKDxzA3MI+ONKSYDef9dJdbrlzN7CWuTgnazWRFPyC7gp9xd5WZ 85 | |qytvBgAAAQBypCjSCjOm63Zkt9G41aELALyjLYq81f0Gle1CJ48kPUlfC7C2h1lCwam7m9eL0Yk4 86 | |y+tgtPhNleoD8Fyg59MUI6KJFaASeFEUguF/OMZGzXaPRUulXtm+xqiU2NWxva21up/q13RwAiyc 87 | |4gjRXpJyuFTqQUTv9eHCgQGoFiRJu0FltiFheaWv+ROoZUQ8L5W6N8bnfa3y88kwEkH8tFI0a8n2 88 | |MuxpnJCWXJRr9QRnEuusbFB805vYpxywpIqNCw2likR54+yXAonegX1LeEastIrmr/UwTXHcAKQY 89 | |p79mHoOzDpBwKMKYhGx627hkI2ttiXYMMHH6qraQje4e6bjBBwAAAQEAg2bFSSt16hYO/Zg+V3V9 90 | |CVx9l8c4eGNVnZys+AkqnX/sa3OPtziQ4Y3lZfXHCmU4rvuy/gIbPXW2lTfd5jR9h2o0Kxq7CfXv 91 | |rZNVGarTmls5NqCIW8EdRY2qEPn70oTWbKCkklMxOUyC+dXErWmJLj3DSIciN3FXq0I//bp97Rkc 92 | |aQChryU8K8/QzOeQlxPwwkwFTh4ATN/542deCuSOSGOHWf+KyBmeVaZwD9EsszGg+k/rXmrBrAZx 93 | |pgJ/rl1OhZ2luGxaaiY3t9KmiXrIbjCsVpRHvhqYAhbU4BYnclX8ED8eHygxKpkZX5XlP6dHPlpD 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 | --------------------------------------------------------------------------------