Skip to content

Instantly share code, notes, and snippets.

@Baccata
Last active September 14, 2021 08:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Baccata/c5be728bff956e0f25d1207f6e990cec to your computer and use it in GitHub Desktop.
Save Baccata/c5be728bff956e0f25d1207f6e990cec to your computer and use it in GitHub Desktop.
JVM/JS/Native Platform-specific implementations of crypto functions required for implementing the AWS signature algorithm.

JVM/JS/Native Platform-specific implementations of hmacSha256.

The CryptoPlatformCompat objects all abide by the same structural interface, which means they can be called from cross-compiled code.

The Binary type alias differs depending on platforms. Think of it as a path-dependant type.

The file names in this gist are annotated with the platforms the files are related to. They should be shoved in relevant directories when copied into an actual Scala project.

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
/**
* @see https://nodejs.org/dist/latest-v7.x/docs/api/crypto.html#crypto_crypto
*/
@js.native
trait Crypto extends js.Object {
def createHash(algorithm: String): Hash = js.native
def createHmac(algorithm: String, key: String): Hmac = js.native
def createHmac(algorithm: String, buffer: Buffer): Hmac = js.native
}
@js.native
@JSImport("crypto", JSImport.Namespace)
object Crypto extends Crypto
@js.native
trait Hash extends js.Any {
def digest(encoding: String): String = js.native
def update(data: String, input_encoding: String = null): Unit = js.native
}
@js.native
trait Hmac extends js.Any {
def digest(encoding: String): String = js.native
def digest(): Buffer = js.native
def update(data: String, input_encoding: String = null): Unit = js.native
}
// On MacOS, you need to: brew install llvm bdw-gc re2 libidn curl. If your system curl is older than 7.56.0,
// you'll need to use the updated brewl-curl when linking & compiling. To do that, create a core/native/local.sbt file, and add:
// nativeCompileOptions += "-I/usr/local/opt/curl/include"
// nativeLinkingOptions += "-L/usr/local/opt/curl/lib"
// where the paths match the output of the `brew install curl` command
@link("crypto")
@unsafe.extern
object crypto {
/**
* Allocates, initializes and returns a digest context.
*/
def EVP_MD_CTX_new(): Ptr[EVP_MD_CTX] = extern
/**
* @return EVP_MD structures for SHA256 digest algorithm.
*/
def EVP_sha256(): Ptr[EVP_MD] = extern
def EVP_DigestInit(ctx: Ptr[EVP_MD_CTX], md: Ptr[EVP_MD]): Unit = extern
def EVP_DigestUpdate(
ctx: Ptr[EVP_MD_CTX],
data: Ptr[CSignedChar],
datalen: unsafe.CSize): Unit = extern
def EVP_DigestFinal(
ctx: Ptr[EVP_MD_CTX],
res: Ptr[CUnsignedChar],
reslen: Ptr[CUnsignedInt]): Unit = extern
def HMAC(
md: Ptr[EVP_MD],
key: CString,
keylen: CSSize,
data: Ptr[CSignedChar],
datalen: CSize,
res: Ptr[CUnsignedChar],
reslen: Ptr[CUnsignedInt]): Unit = extern
}
/**
* The EVP_MD type is a structure for digest method implementation
*/
trait EVP_MD {}
trait EVP_MD_CTX {}
object CryptoPlatformCompat {
type Binary = Buffer
def binaryFromString(str: String): Binary = Buffer.from(str, utf8)
def toHexString(binary: Binary): String =
binary
.entries()
.toIterator
.flatMap(_.lastOption)
.map(n => f"$n%02x")
.mkString
def sha256HexDigest(message: String): String = {
val hash = Crypto.createHash(sha256)
hash.update(message, utf8)
hash.digest(hex)
}
def hmacSha256(data: String, key: Binary): Binary = {
val hmac = Crypto.createHmac(sha256, key)
hmac.update(data)
hmac.digest()
}
val sha256 = "sha256"
val utf8 = "UTF-8"
val hex = "hex"
val binary = "binary"
val ascii = "ascii"
}
import java.security.MessageDigest
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
object CryptoPlatformCompat {
type Binary = Array[Byte]
def binaryFromString(str: String): Binary =
str.getBytes("UTF-8")
def toHexString(binary: Binary): String =
binary.map("%02x".format(_)).mkString
def sha256HexDigest(message: String): String = toHexString {
MessageDigest
.getInstance("SHA-256")
.digest(message.getBytes("UTF-8"))
}
def hmacSha256(data: String, key: Array[Byte]): Array[Byte] = {
val algorithm = "HmacSHA256"
val mac = Mac.getInstance(algorithm)
mac.init(new SecretKeySpec(key, algorithm))
mac.doFinal(data.getBytes("UTF-8"))
}
}
import scala.scalanative.unsafe
import scala.scalanative.unsafe._
object CryptoPlatformCompat {
type Binary = Array[Byte]
def binaryFromString(str: String): Binary =
str.getBytes("UTF-8")
def toHexString(binary: Binary): String =
binary.map("%02x".format(_)).mkString
def sha256HexDigest(message: String): String =
unsafe.Zone { implicit z =>
val outLength: Ptr[CUnsignedInt] = unsafe.alloc[CUnsignedInt]
val digest: Ptr[CUnsignedChar] =
unsafe.alloc[CUnsignedChar](CryptoPlatformCompat.EVP_MAX_MD_SIZE)
val dataLen = message.length.toLong
val cInput: CString = toCString(message)
val ctx = crypto.EVP_MD_CTX_new()
crypto.EVP_DigestInit(ctx, crypto.EVP_sha256())
crypto.EVP_DigestUpdate(ctx, cInput, dataLen)
crypto.EVP_DigestFinal(ctx, digest, outLength)
val out = Array.fill[Byte]((!outLength).toInt)(0)
for (i <- 0 until (!outLength).toInt) out(i) = digest(i.toLong).toByte
out.map("%02x".format(_)).mkString
}
def hmacSha256(data: String, key: Binary): Binary =
unsafe.Zone { implicit z =>
val outLength: Ptr[CUnsignedInt] = unsafe.alloc[CUnsignedInt]
val digest: Ptr[CUnsignedChar] =
unsafe.alloc[CUnsignedChar](CryptoPlatformCompat.EVP_MAX_MD_SIZE)
val cKey = arrayToCString(key)
val keylen = key.length.toLong
val cData: CString = toCString(data)
val datalen = data.length.toLong
crypto.HMAC(crypto.EVP_sha256(),
cKey,
keylen,
cData,
datalen,
digest,
outLength)
val out = Array.fill[Byte]((!outLength).toInt)(0)
for (i <- 0 until (!outLength).toInt) out(i) = digest(i.toLong).toByte
out
}
private def arrayToCString(bytes: Array[Byte])(implicit z: Zone): CString = {
val cstr = z.alloc(bytes.length.toLong + 1)
var c = 0
while (c < bytes.length) {
!(cstr + c.toLong) = bytes(c)
c += 1
}
!(cstr + c.toLong) = 0.toByte
cstr
}
val EVP_MAX_MD_SIZE = 64L
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment