Skip to content

Instantly share code, notes, and snippets.

@marklister
Last active September 20, 2020 23:40
Show Gist options
  • Save marklister/3724617 to your computer and use it in GitHub Desktop.
Save marklister/3724617 to your computer and use it in GitHub Desktop.
Scala Hex / Decimal / Binary calculator / DSL

###Project Moved

This project has moved to https://github.com/marklister/basen

The new version supports Scala 2.10.3 / newer sbt

###Scala Hex / Decimal / Binary / Crypto DSL

I used this DSL / library to complete Dan Boneth's Cryptography I Coursera course. I did all the exercises in Scala. Scala was (IMHO) a pretty good fit for the task and I suspect my solutions performed a bit better than Python based code. Functional programming and crypto prototyping seem to go quite well together.

####Licence

Apache 2, or if you need something else just shout. Please, if you fork this don't add any spoilers (ie exercise specific code).

####Usage

Unfortunately due to issue SI-4684 you can't load this file directly into the REPL. You can try (it might be fixed by now). scala> :load ./BaseN.scala

#####Plan B (best method!):

Save the file BaseN.scala its own directory. Run sbt console from that directory. From the console prompt type import BaseN._; import CryptoOps._

#####Plan C -- use the REPL :paste command


scala> :paste
// Entering paste mode (ctrl-D to finish)
//Paste the file
//Type CTRL-D

// Exiting paste mode, now interpreting.

warning: there were 2 deprecation warnings; re-run with -deprecation for details
defined class BaseN
defined module BaseN
defined class Hex
defined class Decimal
defined class Binary
import BaseN._

Now you can use the REPL as a Binary / Hex / Decimal calculator:

Syntax:

 "String".b  // interpret string as binary

 "String".h  // interpret string as hex

 "String".d  // interpret string as a decimal

 Int.h       // convert Int into a BaseN number (hex in this case)

 you can use either ^ or 'xor' as an xor operator
scala> "10".b
res0: Binary = 00000010 [base: 2]

scala> "10".b * "10".b
res1: BaseN = 00000100 [base: 2]

scala> "0a".h
res2: Hex = 0a [base: 16]

scala> "0a".h <<1
res3: BaseN = 14 [base: 16]

scala> "0a".h <<"10".b
res4: BaseN = 28 [base: 16]

scala> "11111111".b xor "10101010".b
res5: BaseN = 01010101 [base: 2]

scala> "0a".h
res10: Hex = 0a [base: 16]

scala> "0a".h.d
res11: Decimal = 010 [base: 10]

scala> "0a".h.b
res12: Binary = 00001010 [base: 2]
 
ToString

The default toString implemenation is byte orientated, so for example, "256".d is displayed as: 001:255 [base 10]. You can use toRawString or toString(10) to display the number conventionally.

If you use a BigInt operator that I've not implemented you'll wind up with a BigInt. You can convert a BigInt using the .h .d or .b methods. scala> res14.b

CryptoOps

The CryptoOps class adds crypto primatives to Array[Byte] via implicit conversions (ie the pimp my library pattern).

Digest example
scala> "Hello World".getBytes.sha256

res6: Array[Byte] = Array(-91, -111, -90, -44, 11, -12, 32, 64, 74, 1, 23, 51, -49, -73, -79, -112, -42, 44, 101, -65, 11, -51, -93, 43, 87, -78, 119, -39, -83, -97, 20, 110)

scala> res6.h

res7: Hex = a5:91:a6:d4:0b:f4:20:40:4a:01:17:33:cf:b7:b1:90:d6:2c:65:bf:0b:cd:a3:2b:57:b2:77:d9:ad:9f:14:6e [base: 16]
AES example
scala>import BaseN._

import BaseN._

scala>import CryptoOps._

import CryptoOps._

scala>"Hello World".getBytes.pkcs5Pad.aesEncrypt("Short key".getBytes.pkcs5Pad)

res1: CryptoOps = 01:73:69:fa:78:43:38:ae:07:4e:8d:10:c4:3c:cc:fd [base: 16]    

scala>res1.aesDecrypt("Short key".getBytes.pkcs5Pad).h.toByteArray.map(_.toChar)

res3: Array[Char] = Array(H, e, l, l, o,  , W, o, r, l, d, ?, ?, ?, ?, ?)

Enjoy!

/**
* Scala hex / decimal binary calculator
* (c) Mark Lister 2012
* Licence Apache 2.
*/
class BaseN(b: BigInt, val baseN: Int) extends BigInt(b.underlying) {
def n(x: Int) = new BaseN(b, x)
/**
* Return this BaseN as an array of Int normalized in the range 0 -255
*/
def toIntArray: Array[Int] = toByteArray.map(_ + 256).map(_ % 256)
private def toB(n: BigInt): List[Int] = {
if (n == 0) Nil else
(n % 2).toInt :: toB(n / 2)
}
/**
* Return the bit representation of this BaseN
*/
def toBits = toB(this).reverse
/**
* Return this BaseN as a Byte Array with the sign byte removed (if it exists)
*/
implicit override def toByteArray = if (super.toByteArray.head == 0) super.toByteArray.tail else super.toByteArray
override def ^(that: BigInt): BaseN = BaseN(super.^(that), baseN)
def xor(that: BigInt) = ^(that) //Synonym for ^
override def +(that: BigInt) = BaseN(super.+(that), baseN)
override def -(that: BigInt) = BaseN(super.-(that), baseN)
override def *(that: BigInt) = BaseN(super.*(that), baseN)
override def /(that: BigInt) = BaseN(super./(that), baseN)
override def &(that: BigInt) = BaseN(super.&(that), baseN)
override def |(that: BigInt) = BaseN(super.|(that), baseN)
override def <<(n: Int) = BaseN(super.<<(n), baseN)
override def >>(n: Int) = BaseN(super.>>(n), baseN)
/**
* Return the byte wise String representation of this BaseN. For a non byte wise
* orientated version use toString(16) or toRawString
*/
override def toString = {
val len = (scala.Math.log(256) / scala.Math.log(baseN) + 0.999).toInt
def toS(i: Int) = ("0" * len + BigInt(i).toString(baseN)).takeRight(len)
toIntArray.map(toS(_)).mkString(":") + " [base: " + baseN + "]"
}
/**
* Return the conventional String representation of this BaseN
*/
def toRawString = super.toString(baseN)
}
object BaseN {
def apply(b: BigInt, n: Int) = new BaseN(new BigInt(b.underlying), n)
def apply(ba:Array[Byte])= new BaseN(BigInt(ba),16)
// implicit def toByteArray(n: BaseN): Array[Byte] = n.toByteArray //hopefully the same method in the class does this.
implicit def StringToBinary(s: String): Binary = new Binary(BigInt(s, 2))
implicit def StringToHex(s: String): Hex = new Hex(BigInt(s, 16))
implicit def StringToDecimal(s: String): Decimal = new Decimal(BigInt(s, 10))
implicit def BaseNToHex(x: BaseN): Hex = new Hex(x)
implicit def BaseNToHDecimal(x: BaseN): Decimal = new Decimal(x)
implicit def BaseNToBinary(x: BaseN): BaseN = new Binary(x)
implicit def IntToHex(i: Int): Hex = new Hex(BigInt(i)) // to support 0xff etc
implicit def BaseNToInt(n: BaseN): Int = n.toInt
implicit def BigIntToBinary(b: BigInt): Binary = new Binary(b)
implicit def BigIntToHex(b: BigInt): Hex = new Hex(b)
implicit def BigIntToDecimal(b: BigInt): Decimal = new Decimal(b)
implicit def ArrayByteToHex(ba: Array[Byte]): Hex = new Hex(BigInt(ba))
//implicit def SeqByteToHex(sb:Seq[Byte]):Hex=new Hex(BigInt(sb.toArray))
implicit def BaseNToSeqByte(bn:BaseN):Seq[Byte]=bn.toByteArray.toSeq
}
class Hex(b: BigInt) extends BaseN(b, 16) {
def h = this
}
class Decimal(b: BigInt) extends BaseN(b, 10) {
def d = this
}
class Binary(b: BigInt) extends BaseN(b, 2) {
def b = this
}
/**
* CryptoOps is a sort of "RichByteArray" for Crypto primitives
*/
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
class CryptoOps(val b:Array[Byte]){
/**
* Return the sha256 of this CryptOps
*/
def sha256 = digest("SHA-256")
/**
* Return the SHA-1 of this BaseN
*/
def sha1 = digest("SHA-1")
/**
* Return the MD5 of this BaseN
*/
def md5 = digest("MD5")
/**
* Return a Digest of this CryptoOps that is supported by the underlying
* security provider of the JVM (SHA1, SHA256 and MD5 are standard)
* Private but you can change that if you need direct access.
*/
private def digest(d: String):Array[Byte] = {
val md = MessageDigest.getInstance(d)
md.update(b)
md.digest
}
/**
* Pad this CryptoOps to a multiple of 16 bytes by: adding n bytes of value n
* where n is the number of bytes required to make this a multiple of 16.
* Where the block is a multiple of 16, a full block is added.
*/
def pkcs5Pad: CryptoOps = pkcs5Pad(16)
/**
* Pad this block in the same way as pkcs5Pad but with a selectable block size.
*/
def pkcs5Pad(n: Int): CryptoOps = {
val p = n - b.length % n
b ++ (1 to p).map(i => p.toByte)
}
/**
* Return this CryptoOps encrypted using AES in ECB mode
* Returns an Array[Byte]
*/
def aesEncrypt(k: Array[Byte]):Array[Byte] = {
val c = Cipher.getInstance("AES/ECB/NoPadding")
val key = new SecretKeySpec(k, "AES")
c.init(Cipher.ENCRYPT_MODE, key)
c.doFinal(b)
}
/**
* Return this CryptoOps decrypted using AES in ECB Mode
* Returns an Array[Byte]
*/
def aesDecrypt(k: Array[Byte]) = {
val c = Cipher.getInstance("AES/ECB/NoPadding")
val key = new SecretKeySpec(k, "AES")
c.init(Cipher.DECRYPT_MODE, key)
c.doFinal(b)
}
}
object CryptoOps{
def apply(b:Array[Byte])= new CryptoOps(b)
implicit def ArrayByteToCryptoOps(ba:Array[Byte]):CryptoOps= CryptoOps(ba)
implicit def SeqByteToCryptoOps(sb:Seq[Byte]):CryptoOps= CryptoOps(sb.toArray)
implicit def CryptoOpsToByteArray(co:CryptoOps):Array[Byte]= co.b
implicit def CryptoOpsToSeqByte(co:CryptoOps):Seq[Byte]= co.b.toSeq
implicit def BaseNToCryptoOps(bn:BaseN)=CryptoOps(bn.toByteArray)
implicit def CryptoOpsToBaseN(co:CryptoOps)=BaseN(co.b)
implicit def CryptoOpsToHex(co:CryptoOps)=new Hex(BigInt(co.b))
}
@gregwk
Copy link

gregwk commented Mar 27, 2013

Mark,

I just started Dan Boneth's Cryptology I Coursera course two days ago (Mar 25, 2013) and began building a Scala module with similar functionality until I came across your code. This is a great find for me. I will make sure to post this GitHub link to the Cryptology I forum.

Thanks so much!

Greg Kulczycki

@marklister
Copy link
Author

My pleasure Greg, I hope you are enjoying the course. I thought it was excellent especially the programming exercises...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment