SignatureUtils.java

/* ========================================================================
 * PlantUML : a free UML diagram generator
 * ========================================================================
 *
 * (C) Copyright 2009-2024, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 * 
 * If you like this project or if you find it useful, you can support us at:
 * 
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/paypal
 * 
 * This file is part of PlantUML.
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PlantUML distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 *
 * Original Author:  Arnaud Roques
 *
 *
 */
package net.sourceforge.plantuml.utils;

// ::comment when __TEAVM__
import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

import net.sourceforge.plantuml.code.AsciiEncoder;
import net.sourceforge.plantuml.log.Logme;
import net.sourceforge.plantuml.security.SFile;
// ::done

// ::uncomment when __TEAVM__
//import org.teavm.jso.JSBody;
// ::done

public class SignatureUtils {

	// ::comment when __TEAVM__
	public static String toHexString(byte data[]) {
		final StringBuilder sb = new StringBuilder(data.length * 2);
		for (byte b : data)
			sb.append(String.format("%02x", b));

		return sb.toString();
	}

	public static String getMD5Hex(String s) {
		try {
			final byte[] digest = getMD5raw(s);
			assert digest.length == 16;
			return toHexString(digest);
		} catch (NoSuchAlgorithmException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		} catch (UnsupportedEncodingException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		}
	}

	public static synchronized byte[] getMD5raw(String s)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		final MessageDigest msgDigest = MessageDigest.getInstance("MD5");
		msgDigest.update(s.getBytes(UTF_8));
		return msgDigest.digest();
	}

	public static synchronized byte[] salting(String pass, byte[] salt)
			throws NoSuchAlgorithmException, InvalidKeySpecException {
		final int iterations = 500;
		final int keyLength = 512;
		final SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
		final PBEKeySpec spec = new PBEKeySpec(pass.toCharArray(), salt, iterations, keyLength);
		final SecretKey key = skf.generateSecret(spec);
		final byte[] tmp = key.getEncoded();
		return tmp;
	}

	public static String getSignature(String s) {
		try {
			final byte[] digest = getMD5raw(s);
			return toString(digest);
		} catch (NoSuchAlgorithmException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		} catch (UnsupportedEncodingException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		}
	}

	public static String toString(byte data[]) {
		final AsciiEncoder coder = new AsciiEncoder();
		return coder.encode(data);
	}

	public static String getSHA512Hex(String s) {
		try {
			final byte[] digest = getSHA512raw(s);
			assert digest.length == 64;
			return toHexString(digest);
		} catch (NoSuchAlgorithmException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		} catch (UnsupportedEncodingException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		}
	}

	public static byte[] getSHA512raw(String s) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		return getSHA512raw(s.getBytes(UTF_8));
	}

	public static synchronized byte[] getSHA512raw(byte data[])
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		final MessageDigest msgDigest = MessageDigest.getInstance("SHA-512");
		msgDigest.update(data);
		return msgDigest.digest();
	}

	public static String getSignatureSha512(SFile f) throws IOException {
		try (InputStream is = f.openFile()) {
			return getSignatureSha512(is);
		}
	}

	public static synchronized String getSignatureSha512(InputStream is) throws IOException {
		try {
			final MessageDigest msgDigest = MessageDigest.getInstance("SHA-512");
			int read = 0;
			while ((read = is.read()) != -1)
				msgDigest.update((byte) read);

			final byte[] digest = msgDigest.digest();
			return toString(digest);
		} catch (NoSuchAlgorithmException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		} catch (UnsupportedEncodingException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		}
	}

	public static String getSignatureWithoutImgSrc(String s) {
		s = getSignature(purge(s));
		return s;
	}

	public static String purge(String s) {
		final String regex = "(?i)\\<img\\s+src=\"(?:[^\"]+[/\\\\])?([^/\\\\\\d.]+)\\d*(\\.\\w+)\"/\\>";
		s = s.replaceAll(regex, "<img src=\"$1$2\"/>");
		final String regex2 = "(?i)image=\"(?:[^\"]+[/\\\\])?([^/\\\\\\d.]+)\\d*(\\.\\w+)\"";
		s = s.replaceAll(regex2, "image=\"$1$2\"");
		return s;
	}

	public static synchronized String getSignature(SFile f) throws IOException {
		try (final InputStream is = f.openFile()) {
			final MessageDigest msgDigest = MessageDigest.getInstance("MD5");
			if (is == null)
				throw new FileNotFoundException();

			int read = -1;
			while ((read = is.read()) != -1)
				msgDigest.update((byte) read);

			final byte[] digest = msgDigest.digest();
			return toString(digest);
		} catch (NoSuchAlgorithmException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		} catch (UnsupportedEncodingException e) {
			Logme.error(e);
			throw new UnsupportedOperationException(e);
		}
	}
	// ::done

	// ::uncomment when __TEAVM__
//	/**
//	 * Computes MD5 hash of a string and returns it as a hex string.
//	 * Uses a pure JavaScript MD5 implementation for TeaVM compatibility.
//	 */
//	public static String getMD5Hex(String s) {
//		return md5Native(s);
//	}
//
//	/**
//	 * Native JavaScript MD5 implementation.
//	 * Based on the well-known MD5 algorithm adapted for browser environments.
//	 */
//	@JSBody(params = { "string" }, script = 
//		"function md5(string) {" +
//		"  function md5cycle(x, k) {" +
//		"    var a = x[0], b = x[1], c = x[2], d = x[3];" +
//		"    a = ff(a, b, c, d, k[0], 7, -680876936);" +
//		"    d = ff(d, a, b, c, k[1], 12, -389564586);" +
//		"    c = ff(c, d, a, b, k[2], 17, 606105819);" +
//		"    b = ff(b, c, d, a, k[3], 22, -1044525330);" +
//		"    a = ff(a, b, c, d, k[4], 7, -176418897);" +
//		"    d = ff(d, a, b, c, k[5], 12, 1200080426);" +
//		"    c = ff(c, d, a, b, k[6], 17, -1473231341);" +
//		"    b = ff(b, c, d, a, k[7], 22, -45705983);" +
//		"    a = ff(a, b, c, d, k[8], 7, 1770035416);" +
//		"    d = ff(d, a, b, c, k[9], 12, -1958414417);" +
//		"    c = ff(c, d, a, b, k[10], 17, -42063);" +
//		"    b = ff(b, c, d, a, k[11], 22, -1990404162);" +
//		"    a = ff(a, b, c, d, k[12], 7, 1804603682);" +
//		"    d = ff(d, a, b, c, k[13], 12, -40341101);" +
//		"    c = ff(c, d, a, b, k[14], 17, -1502002290);" +
//		"    b = ff(b, c, d, a, k[15], 22, 1236535329);" +
//		"    a = gg(a, b, c, d, k[1], 5, -165796510);" +
//		"    d = gg(d, a, b, c, k[6], 9, -1069501632);" +
//		"    c = gg(c, d, a, b, k[11], 14, 643717713);" +
//		"    b = gg(b, c, d, a, k[0], 20, -373897302);" +
//		"    a = gg(a, b, c, d, k[5], 5, -701558691);" +
//		"    d = gg(d, a, b, c, k[10], 9, 38016083);" +
//		"    c = gg(c, d, a, b, k[15], 14, -660478335);" +
//		"    b = gg(b, c, d, a, k[4], 20, -405537848);" +
//		"    a = gg(a, b, c, d, k[9], 5, 568446438);" +
//		"    d = gg(d, a, b, c, k[14], 9, -1019803690);" +
//		"    c = gg(c, d, a, b, k[3], 14, -187363961);" +
//		"    b = gg(b, c, d, a, k[8], 20, 1163531501);" +
//		"    a = gg(a, b, c, d, k[13], 5, -1444681467);" +
//		"    d = gg(d, a, b, c, k[2], 9, -51403784);" +
//		"    c = gg(c, d, a, b, k[7], 14, 1735328473);" +
//		"    b = gg(b, c, d, a, k[12], 20, -1926607734);" +
//		"    a = hh(a, b, c, d, k[5], 4, -378558);" +
//		"    d = hh(d, a, b, c, k[8], 11, -2022574463);" +
//		"    c = hh(c, d, a, b, k[11], 16, 1839030562);" +
//		"    b = hh(b, c, d, a, k[14], 23, -35309556);" +
//		"    a = hh(a, b, c, d, k[1], 4, -1530992060);" +
//		"    d = hh(d, a, b, c, k[4], 11, 1272893353);" +
//		"    c = hh(c, d, a, b, k[7], 16, -155497632);" +
//		"    b = hh(b, c, d, a, k[10], 23, -1094730640);" +
//		"    a = hh(a, b, c, d, k[13], 4, 681279174);" +
//		"    d = hh(d, a, b, c, k[0], 11, -358537222);" +
//		"    c = hh(c, d, a, b, k[3], 16, -722521979);" +
//		"    b = hh(b, c, d, a, k[6], 23, 76029189);" +
//		"    a = hh(a, b, c, d, k[9], 4, -640364487);" +
//		"    d = hh(d, a, b, c, k[12], 11, -421815835);" +
//		"    c = hh(c, d, a, b, k[15], 16, 530742520);" +
//		"    b = hh(b, c, d, a, k[2], 23, -995338651);" +
//		"    a = ii(a, b, c, d, k[0], 6, -198630844);" +
//		"    d = ii(d, a, b, c, k[7], 10, 1126891415);" +
//		"    c = ii(c, d, a, b, k[14], 15, -1416354905);" +
//		"    b = ii(b, c, d, a, k[5], 21, -57434055);" +
//		"    a = ii(a, b, c, d, k[12], 6, 1700485571);" +
//		"    d = ii(d, a, b, c, k[3], 10, -1894986606);" +
//		"    c = ii(c, d, a, b, k[10], 15, -1051523);" +
//		"    b = ii(b, c, d, a, k[1], 21, -2054922799);" +
//		"    a = ii(a, b, c, d, k[8], 6, 1873313359);" +
//		"    d = ii(d, a, b, c, k[15], 10, -30611744);" +
//		"    c = ii(c, d, a, b, k[6], 15, -1560198380);" +
//		"    b = ii(b, c, d, a, k[13], 21, 1309151649);" +
//		"    a = ii(a, b, c, d, k[4], 6, -145523070);" +
//		"    d = ii(d, a, b, c, k[11], 10, -1120210379);" +
//		"    c = ii(c, d, a, b, k[2], 15, 718787259);" +
//		"    b = ii(b, c, d, a, k[9], 21, -343485551);" +
//		"    x[0] = add32(a, x[0]);" +
//		"    x[1] = add32(b, x[1]);" +
//		"    x[2] = add32(c, x[2]);" +
//		"    x[3] = add32(d, x[3]);" +
//		"  }" +
//		"  function cmn(q, a, b, x, s, t) {" +
//		"    a = add32(add32(a, q), add32(x, t));" +
//		"    return add32((a << s) | (a >>> (32 - s)), b);" +
//		"  }" +
//		"  function ff(a, b, c, d, x, s, t) {" +
//		"    return cmn((b & c) | ((~b) & d), a, b, x, s, t);" +
//		"  }" +
//		"  function gg(a, b, c, d, x, s, t) {" +
//		"    return cmn((b & d) | (c & (~d)), a, b, x, s, t);" +
//		"  }" +
//		"  function hh(a, b, c, d, x, s, t) {" +
//		"    return cmn(b ^ c ^ d, a, b, x, s, t);" +
//		"  }" +
//		"  function ii(a, b, c, d, x, s, t) {" +
//		"    return cmn(c ^ (b | (~d)), a, b, x, s, t);" +
//		"  }" +
//		"  function md51(s) {" +
//		"    var n = s.length," +
//		"    state = [1732584193, -271733879, -1732584194, 271733878], i;" +
//		"    for (i = 64; i <= s.length; i += 64) {" +
//		"      md5cycle(state, md5blk(s.substring(i - 64, i)));" +
//		"    }" +
//		"    s = s.substring(i - 64);" +
//		"    var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];" +
//		"    for (i = 0; i < s.length; i++)" +
//		"      tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);" +
//		"    tail[i >> 2] |= 0x80 << ((i % 4) << 3);" +
//		"    if (i > 55) {" +
//		"      md5cycle(state, tail);" +
//		"      for (i = 0; i < 16; i++) tail[i] = 0;" +
//		"    }" +
//		"    tail[14] = n * 8;" +
//		"    md5cycle(state, tail);" +
//		"    return state;" +
//		"  }" +
//		"  function md5blk(s) {" +
//		"    var md5blks = [], i;" +
//		"    for (i = 0; i < 64; i += 4) {" +
//		"      md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) +" +
//		"        (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);" +
//		"    }" +
//		"    return md5blks;" +
//		"  }" +
//		"  var hex_chr = '0123456789abcdef'.split('');" +
//		"  function rhex(n) {" +
//		"    var s = '', j = 0;" +
//		"    for (; j < 4; j++)" +
//		"      s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];" +
//		"    return s;" +
//		"  }" +
//		"  function hex(x) {" +
//		"    for (var i = 0; i < x.length; i++)" +
//		"      x[i] = rhex(x[i]);" +
//		"    return x.join('');" +
//		"  }" +
//		"  function add32(a, b) {" +
//		"    return (a + b) & 0xFFFFFFFF;" +
//		"  }" +
//		"  return hex(md51(unescape(encodeURIComponent(string))));" +
//		"}" +
//		"return md5(string);")
//	private static native String md5Native(String string);
	// ::done

}