aboutsummaryrefslogblamecommitdiff
path: root/shared/src/main/scala/Identicon.scala
blob: df4c4241f13f1950fcfc35e8f82e8e763814a78c (plain) (tree)
1
2
3
4
5
6
7
8






                       
                                                                     



                                                                                

                                                        




                                                                                

                       

















                                                                             





                                                                
 


















                                                        

       



                

                             
                 
                 

                                  
               








                                  
         




                               
       



             
     
                                    















                                   



































                                                                                            
 
package identicon

import java.lang.Math
import java.util.Base64

trait Identicon {

  private def lrot(x: Int, c: Int): Int = (x << c) | (x >>> (32 - c))

  // NB: this implementation has a bug and does NOT correspond to the actual MD5
  // specification. It does however produce a hash that appears to be good
  // enough for generating identicons.
  private def md5(message: Array[Byte]): Array[Byte] = {
    val s = Array[Int](
      7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20,
      5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4,
      11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6,
      10, 15, 21
    )

    val k = Array[Int](
      0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
      0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
      0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
      0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
      0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
      0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
      0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
      0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
      0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
      0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
      0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
    )

    var a0 = 0x67452301
    var b0 = 0xefcdab89
    var c0 = 0x98badcfe
    var d0 = 0x10325476

    // number of 64-byte (512-bit) blocks that will be processed
    val nblocks = ((message.size + 8) >>> 6) + 1
    val bytes = new Array[Byte](nblocks << 6)

    Array.copy(message, 0, bytes, 0, message.size)
    bytes(message.size) = 0x80.toByte

    val bitLength: Long = message.size.toLong << 3
    bytes(bytes.size - 8) = (bitLength).toByte
    bytes(bytes.size - 7) = (bitLength >>> 8).toByte
    bytes(bytes.size - 6) = (bitLength >>> 16).toByte
    bytes(bytes.size - 5) = (bitLength >>> 24).toByte
    // bytes(bytes.size - 4) = (bitLength >>> 32).toByte
    // bytes(bytes.size - 3) = (bitLength >>> 40).toByte
    // bytes(bytes.size - 2) = (bitLength >>> 48).toByte
    // bytes(bytes.size - 1) = (bitLength >>> 56).toByte

    for (block <- 0 until nblocks) {
      val m = new Array[Int](16)
      var index = block << 6
      for (i <- 0 until 16) {
        m(i) = bytes(index) |
          (bytes(index + 1) << 8) |
          (bytes(index + 2) << 16) |
          (bytes(index + 3) << 24)
        index += 4
      }

      var a = a0
      var b = b0
      var c = c0
      var d = d0

      for (i <- 0 until 64) {
        var f = 0
        var g = 0
        if (i < 16) {
          f = (b & c) | ((~b) & d)
          g = i
        } else if (i < 32) {
          f = (d & b) | ((~d) & c)
          g = (5 * i + 1) & 0x0f
        } else if (i < 48) {
          f = b ^ c ^ d
          g = (3 * i + 5) & 0x0f
        } else {
          f = c ^ (b | (~d))
          g = (7 * i) & 0x0f
        }
        f = f + a + k(i) + m(g)
        a = d
        d = c
        c = b
        b = b + lrot(f, s(i))
      }
      a0 += a
      b0 += b
      c0 += c
      d0 += d
    }
    val digest = new Array[Byte](16)
    digest(0) = a0.toByte
    digest(1) = (a0 >>> 8).toByte
    digest(2) = (a0 >>> 16).toByte
    digest(3) = (a0 >>> 24).toByte
    digest(4) = b0.toByte
    digest(5) = (b0 >>> 8).toByte
    digest(6) = (b0 >>> 16).toByte
    digest(7) = (b0 >>> 24).toByte
    digest(8) = c0.toByte
    digest(9) = (c0 >>> 8).toByte
    digest(10) = (c0 >>> 16).toByte
    digest(11) = (c0 >>> 24).toByte
    digest(12) = d0.toByte
    digest(13) = (d0 >>> 8).toByte
    digest(14) = (d0 >>> 16).toByte
    digest(15) = (d0 >>> 24).toByte
    digest
  }

  def svg(name: String): String = {
    val hash = md5(name.getBytes("UTF-8"))
    val builder = new StringBuilder
    val color = {
      val r = hash(0) & 0xff
      val g = hash(1) & 0xff
      val b = hash(2) & 0xff
      f"#$r%02x$g%02x$b%02x"
    }
    val style = s"fill:$color;stroke:$color;stroke-width:0.05"
    builder ++= s"""<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5">"""
    for (x <- 0 until 2) {
      for (y <- 0 until 5) {
        if (((hash(x) >>> y) & 0x01) != 0) {
          builder ++= s"""<rect x="$x" y="$y" width="1" height="1" style="$style"/>"""
          builder ++= s"""<rect x="${4 - x}" y="$y" width="1" height="1" style="$style"/>"""
        }
      }
    }
    for (y <- 0 until 5) {
      if (((hash(2) >>> y) & 0x01) != 0) {
        builder ++= s"""<rect x="2" y="$y" width="1" height="1" style="$style"/>"""
      }
    }
    builder ++= "</svg>"
    builder.result()
  }

  def url(name: String): String = {
    val b64 = Base64.getEncoder().encodeToString(svg(name).getBytes("UTF-8"))
    s"data:image/svg+xml;base64,$b64"
  }

}