git » sdk » main » tree

[main] / borogove / Sha1.hx

/*
 * Copyright (C)2005-2019 Haxe Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package borogove;

import haxe.io.Bytes;
#if java
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
#end

/**
	Creates a Sha1 of a String or Bytes.
**/
class Sha1 {
	#if hl
	public static function encode(s:String):String {
		var out = haxe.io.Bytes.alloc(20);
		@:privateAccess hl.Format.digest(out.b, s.bytes, s.length, 256 | 1);
		return out.toHex();
	}

	public static function make(b:haxe.io.Bytes):haxe.io.Bytes {
		var out = haxe.io.Bytes.alloc(20);
		@:privateAccess hl.Format.digest(out.b, b.b, b.length, 1);
		return out;
	}

	#elseif java
	public static function encode(s:String):String {
		return Bytes.ofData(digest((cast s : java.NativeString).getBytes(StandardCharsets.UTF_8))).toHex();
	}

	public static function make(b:haxe.io.Bytes):haxe.io.Bytes {
		return Bytes.ofData(digest(b.getData()));
	}

	inline static function digest(b:haxe.io.BytesData):haxe.io.BytesData {
		return MessageDigest.getInstance("SHA-1").digest(b);
	}

	#elseif php
	public static inline function encode(s:String):String {
		return php.Global.sha1(s);
	}

	public static inline function make(b:haxe.io.Bytes):haxe.io.Bytes {
		return Bytes.ofData(php.Global.sha1(b.getData(), true));
	}

	#else
	public static function encode(s:String):String {
		var sh = new Sha1();
		#if target.unicode
		sh.update(haxe.io.Bytes.ofString(s));
		#else
		var b = haxe.io.Bytes.alloc(s.length);
		for (i in 0...s.length) b.set(i, StringTools.fastCodeAt(s, i) & 0xFF);
		sh.update(b);
		#end
		return sh.digest().toHex();
	}

	public static function make(b:haxe.io.Bytes):haxe.io.Bytes {
		var sh = new Sha1();
		sh.update(b);
		return sh.digest();
	}
	#end

	#if java
	var digest:java.security.MessageDigest;

	public function new() {
		digest = java.security.MessageDigest.getInstance("SHA-1");
	}

	public function update(b:haxe.io.Bytes):Void {
		digest.update(b.getData());
	}

	public function digest():haxe.io.Bytes {
		return Bytes.ofData(digest.digest());
	}

	#elseif php
	var context:Dynamic;

	public function new() {
		context = php.Global.hash_init("sha1");
	}

	public function update(b:haxe.io.Bytes):Void {
		php.Global.hash_update(context, b.getData());
	}

	public function digest():haxe.io.Bytes {
		return Bytes.ofData(php.Global.hash_final(context, true));
	}

	#else
	var h:Array<Int>;
	var buffer:haxe.io.Bytes;
	var bufferLen:Int;
	var totalLen:haxe.Int64;

	public function new() {
		h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
		buffer = haxe.io.Bytes.alloc(64);
		bufferLen = 0;
		totalLen = 0;
	}

	public function update(b:haxe.io.Bytes):Void {
		if (b.length == 0) return;

		totalLen = totalLen + b.length;

		var pos = 0;
		var len = b.length;
		while (len > 0) {
			var copyLen = len < (64 - bufferLen) ? len : (64 - bufferLen);
			buffer.blit(bufferLen, b, pos, copyLen);
			bufferLen += copyLen;
			pos += copyLen;
			len -= copyLen;

			if (bufferLen == 64) {
				processBlock();
				bufferLen = 0;
			}
		}
	}

	public function digest():haxe.io.Bytes {
		buffer.set(bufferLen, 0x80);
		bufferLen++;

		if (bufferLen > 56) {
			while (bufferLen < 64) {
				buffer.set(bufferLen, 0);
				bufferLen++;
			}
			processBlock();
			bufferLen = 0;
		}

		while (bufferLen < 56) {
			buffer.set(bufferLen, 0);
			bufferLen++;
		}

		final bitLen = totalLen * 8;
		buffer.set(56, haxe.Int64.toInt((bitLen >>> 56) & 0xFF));
		buffer.set(57, haxe.Int64.toInt((bitLen >>> 48)) & 0xFF);
		buffer.set(58, haxe.Int64.toInt((bitLen >>> 40)) & 0xFF);
		buffer.set(59, haxe.Int64.toInt((bitLen >>> 32)) & 0xFF);
		buffer.set(60, haxe.Int64.toInt((bitLen >>> 24)) & 0xFF);
		buffer.set(61, haxe.Int64.toInt((bitLen >>> 16)) & 0xFF);
		buffer.set(62, haxe.Int64.toInt((bitLen >>> 8)) & 0xFF);
		buffer.set(63, haxe.Int64.toInt(bitLen) & 0xFF);

		processBlock();

		var out = haxe.io.Bytes.alloc(20);
		for (i in 0...5) {
			out.set(i * 4, h[i] >>> 24);
			out.set(i * 4 + 1, (h[i] >> 16) & 0xFF);
			out.set(i * 4 + 2, (h[i] >> 8) & 0xFF);
			out.set(i * 4 + 3, h[i] & 0xFF);
		}
		return out;
	}

	function processBlock():Void {
		var w = new Array<Int>();
		for (j in 0...16) {
			w[j] = (buffer.get(j * 4) << 24) |
			           ((buffer.get(j * 4 + 1) & 0xFF) << 16) |
			           ((buffer.get(j * 4 + 2) & 0xFF) << 8) |
			           (buffer.get(j * 4 + 3) & 0xFF);
		}

		var a = h[0];
		var b = h[1];
		var c = h[2];
		var d = h[3];
		var e = h[4];

		for (j in 0...80) {
			if (j >= 16) {
				w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
			}
			var t = rol(a, 5) + ft(j, b, c, d) + e + w[j] + kt(j);
			e = d;
			d = c;
			c = rol(b, 30);
			b = a;
			a = t;
		}

		h[0] += a;
		h[1] += b;
		h[2] += c;
		h[3] += d;
		h[4] += e;
	}

	/**
		Bitwise rotate a 32-bit number to the left
	**/
	inline function rol(num:Int, cnt:Int):Int {
		return (num << cnt) | (num >>> (32 - cnt));
	}

	/**
		Perform the appropriate triplet combination function for the current iteration
	**/
	function ft(t:Int, b:Int, c:Int, d:Int):Int {
		if (t < 20)
			return (b & c) | ((~b) & d);
		if (t < 40)
			return b ^ c ^ d;
		if (t < 60)
			return (b & c) | (b & d) | (c & d);
		return b ^ c ^ d;
	}

	/**
		Determine the appropriate additive constant for the current iteration
	**/
	function kt(t:Int):Int {
		if (t < 20)
			return 0x5A827999;
		if (t < 40)
			return 0x6ED9EBA1;
		if (t < 60)
			return 0x8F1BBCDC;
		return 0xCA62C1D6;
	}
	#end
}