Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jprystowsky committed Jul 20, 2016
0 parents commit 51fd60c
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 0 deletions.
75 changes: 75 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Created by .ignore support plugin (hsz.mobi)
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml

# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml

# Gradle:
.idea/gradle.xml
.idea/libraries

# Mongo Explorer plugin:
.idea/mongoSettings.xml

## File-based project format:
*.iws

## Plugin-specific files:

# IntelliJ
/out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Scala template
*.class
*.log

# sbt specific
.cache
target/.history
.lib/
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/

# Scala-IDE specific
.scala_dependencies
.worksheet
### SBT template
# Simple Build Tool
# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control

target/
lib_managed/
src_managed/
project/boot/
.history
.cache

6 changes: 6 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name := "rsaex"

version := "1.0"

scalaVersion := "2.11.8"

1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version = 0.13.8
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logLevel := Level.Warn
72 changes: 72 additions & 0 deletions src/main/scala/MathHelper.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import scala.collection.mutable.ArrayBuffer
import scala.util.Random

object MathHelper {
/**
* Find the multiplicative inverse of a number a over the integers modulo n
* See:
* https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers
* https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Code
*/
def modularInverse(a: Int, n: Int): Option[Int] = {
var t = 0
var newT = 1
var r = n
var newR = a % n

while (newR != 0) {
val quotient = r / newR

val thisT = t
t = newT
newT = thisT - quotient * newT

val thisR = r
r = newR
newR = thisR - quotient * newR
}

if (r > 1) return None // a is not invertible

if (t < 0) t += n

Some(t)
}

// Euler's totient for n = pq, primes p and q
// https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation
// https://en.wikipedia.org/wiki/Euler%27s_totient_function
def eulerTotient(p: Int, q: Int) = (p - 1) * (q - 1)

// These algorithms are not guaranteed to succeed
def generatePrimes(num: Int, max: Int): Seq[Int] = {
val primes = new ArrayBuffer[Int](num)

for (i <- 1 to num) {
var testPrime = 0

do {
testPrime = Random.nextInt(max)
} while (!isPrime(testPrime))

primes += testPrime
}

primes
}
def generatePrime(max: Int) = generatePrimes(1, max).head

def isPrime(n: Int): Boolean = {
if (n < 2) return false

if (n == 2) return true

if (n % 2 == 0) return false

for (i <- 3 to ceilRoot(n) by 2) if (n % i == 0) return false

true
}

def ceilRoot(n: Int): Int = Math.ceil(Math.sqrt(n.toDouble)).toInt
}
55 changes: 55 additions & 0 deletions src/main/scala/RsaBreaker.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import scala.collection.mutable.ListBuffer

trait RsaBreaker {
def recoverKey(publicKey: RsaPublicKey): RsaKey
def bruteForceDecrypt(m: String, publicKey: RsaPublicKey): String
}

object RsaBreaker extends RsaBreaker {
// todo: removeme: Suggest (mostly?) gutting, otherwise the challenge becomes much smaller (math understanding portion removed)
// This presumes n = p * q
override def recoverKey(publicKey: RsaPublicKey): RsaKey = {
val factored = factor(publicKey.n)

val p = factored.head._1
val q = factored.tail.head._1

val phiN = MathHelper.eulerTotient(p, q)

val d = MathHelper.modularInverse(RsaCryptosystem.e, phiN).get

RsaKey(publicKey, d, (p, q), phiN)

/**
* todo: removeme
*
* Alternate solution (cheating, less fun, does not demonstrate purpose of e):
*
* RsaCryptosystem.generateKey(p, q)
*/
}

// todo: removeme: Suggest leaving this and having kids use it; it's just a convenience method anyway
override def bruteForceDecrypt(m: String, publicKey: RsaPublicKey): String = RsaCryptosystem.decrypt(m, recoverKey(publicKey))

// todo: removeme: Suggest entirely gutting. This is the crux of the RSA problem.
private def factor(n: Int): Seq[(Int, Int)] = {
val factors = for (i <- 2 until n if n % i == 0) yield i

val res: ListBuffer[(Int, Int)] = ListBuffer()

var x = n
for (i <- factors) {
var c = 0

while (x % i == 0) {
c += 1
x /= i
}

if (c > 0) res += ((i, c))
}

res
}
}
64 changes: 64 additions & 0 deletions src/main/scala/RsaCryptosystem.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* A real cryptosystem doesn't look like this.
* This is not a real RSA implementation.
* But it's sure fun anyway. :)
*/
trait RsaCryptosystem {
def generateKey(p: Int, q: Int): RsaKey
def generateRandomKey: RsaKey

def encrypt(m: Int, e: Int, n: Int): Int
def decrypt(m: Int, d: Int, n: Int): Int

def encrypt(m: Seq[Int], e: Int, n: Int): Seq[Int]
def decrypt(m: Seq[Int], d: Int, n: Int): Seq[Int]

def encrypt(m: String, e: Int, n: Int): String
def decrypt(m: String, d: Int, n: Int): String

def encrypt(m: String, key: RsaKey): String
def decrypt(m: String, key: RsaKey): String
}

object RsaCryptosystem extends RsaCryptosystem {
private final val messageStringJoinVal = " "

// The best prime number between 16 and 18
final val e = 17

// To keep things sane
final val primeMax = 256

// Generate an RSA key using given primes
override def generateKey(p: Int, q: Int): RsaKey = {
val n = p * q

val phiN = MathHelper.eulerTotient(p, q)

// Secret exponent d where e * d is congruent to 1 mod phi(n)
// This could fail (on the .get if it's None) cause reasons if things aren't just so (rare but sometimes)
val d = MathHelper.modularInverse(RsaCryptosystem.e, phiN).get

RsaKey(RsaPublicKey(n, RsaCryptosystem.e), d, (p, q), phiN)
}

// Generate an RSA key using random primes
override def generateRandomKey: RsaKey = {
val primes = MathHelper.generatePrimes(2, RsaCryptosystem.primeMax)

generateKey(primes.head, primes.tail.head)
}

// Encryption and decryption are the same!
override def encrypt(m: Int, e: Int, n: Int): Int = BigInt(m).pow(e).mod(n).toInt
override def decrypt(m: Int, d: Int, n: Int): Int = encrypt(m, d, n)

override def encrypt(m: Seq[Int], e: Int, n: Int): Seq[Int] = for (x <- m) yield encrypt(x, e, n)
override def decrypt(m: Seq[Int], d: Int, n: Int): Seq[Int] = for (x <- m) yield decrypt(x, d, n)

override def encrypt(m: String, e: Int, n: Int): String = encrypt(m.toCharArray.map(_.toInt), e, n).mkString(messageStringJoinVal)
override def decrypt(m: String, d: Int, n: Int): String = decrypt(m.split(messageStringJoinVal).map(_.toInt), d, n).map(_.toChar).mkString

override def encrypt(m: String, key: RsaKey): String = encrypt(m, key.publicKey.e, key.publicKey.n)
override def decrypt(m: String, key: RsaKey): String = decrypt(m, key.privateKey, key.publicKey.n)
}
84 changes: 84 additions & 0 deletions src/main/scala/RsaEx.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
object RsaEx extends App {
/**
* RSA is a common public key encryption algorithm that is based on the idea that we can
* multiply two really big prime numbers together easily, but if you give someone
* the product of two large primes, it's hard to factor it apart again.
*
* We need to learn about modular arithmetic first. This is the arithmetic of a clock:
* 23:00 (11 PM) + 7 hours = 30:00 = 06:00 (6 AM). Another way to say this is that
* 30 is congruent to 6 (mod 24), or that 30 mod 24 equals 6, or in Scala:
* 6 == 30 % 24
*
* We'll be seeing a lot of numbers "mod" (%) another number.
*
* Here is how RSA works:
*
* Alice wants to send a message to Bob.
*
* So Bob picks two huge random prime numbers, p and q. He keeps these a secret.
*
* Bob multiplies them together to get n = p * q. n will be public.
*
* Bob computes phi(n) = (p - 1) * (q - 1) and keeps this as a secret. Phi(n) is called Euler's
* totient function (read about it later!).
*
* Bob picks a value e between 1 and phi(n) such that phi(n) and e have no common factors; here Bob
* chooses 17. e will be public.
*
* Bob calculates the secret key (which must be kept secret) d = e inverse mod phi(n) -- the secret
* value that will undo his encryption, because e * d = 1 mod phi(n).
*
* Finally, Bob gives Alice (n, e) as the public key and keeps (d, n, e) as the private key for himself.
*
* Alice now takes her message X and encrypts the message as C = Math.pow(X, e) % n. She sends this to Bob.
*
* Bob decrypts the message as M back to
* M = Math.pow(C, d) % n
* = Math.pow(Math.pow(X, e), d) % n
* = Math.pow(X, e * d) % n
* = X % n
*
* In a real cryptosystem, p and q are chosen to be so big that it's hard to factor n. In a toy cryptosystem
* like this one, we can use small primes to make things easier (possible).
*
*/

/**
* Let's demonstrate RSA using a random key to get the hang of things.
* This MIGHT fail. If it does, just run it again.
* This code deliberately uses very small numbers. That's what makes it possible for us to break.
*/
val randomKey = RsaCryptosystem.generateRandomKey
Console.println(s"We randomly generated this key:\n$randomKey")

val cleartext = "hello, hackers"
Console.println(s"Cleartext: $cleartext")

val ciphertext = RsaCryptosystem.encrypt(cleartext, randomKey)
Console.println(s"Ciphertext: $ciphertext")

val decryptedCleartext = RsaCryptosystem.decrypt(ciphertext, randomKey)
Console.println(s"Decrypted back: $decryptedCleartext")

/**
* You have stolen the following ciphertext:
* 63 12471 17384 19150 3861 17806 15090 5270
*
* This was encrypted using an RSA key with the following public key:
* n = 19153
* e = 17
*
* Break the key and decrypt the ciphertext to get the flag
*/

/**
* todo: removeme:
* Suggest removing these three lines or gutting severely;
* another valid solution might not include RsaBreaker at all but just procedural code of same purpose
*
* Flag decrypts to: harmonic
*/
val encryptedFlag = "63 12471 17384 19150 3861 17806 15090 5270"
val decryptedFlag = RsaBreaker.bruteForceDecrypt(encryptedFlag, RsaPublicKey(n = 19153, e = 17))
Console.println(s"Decrypted to: $decryptedFlag")
}
17 changes: 17 additions & 0 deletions src/main/scala/RsaKey.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
case class RsaPublicKey(n: Int, e: Int)
case class RsaKey(publicKey: RsaPublicKey, privateKey: Int, primes: (Int, Int), eulerTotient: Int) {
override def toString: String =
s"""
|---------- Public ----------
| n: ${publicKey.n}
| e: ${publicKey.e}
|---------- Private ---------
| d: $privateKey
|
| p: ${primes._1}
| q: ${primes._2}
|
|phi(n): $eulerTotient
|----------------------------
""".stripMargin
}

0 comments on commit 51fd60c

Please sign in to comment.