All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ntc.jcrypto.sss.SSS Maven / Gradle / Ivy

/*
 * Copyright 2020 nghiatc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ntc.jcrypto.sss;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.*;
import javax.xml.bind.DatatypeConverter;

/**
 *
 * @author nghiatc
 * @since Jan 3, 2020
 */
public class SSS {
    // https://primes.utm.edu/lists/2small/200bit.html
    // PRIME = 2^n - k = 2^256 - 189
    private static final BigInteger PRIME = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639747");
    private Random rand = new SecureRandom();
    
    /**
     * Returns a new array of secret shares (encoding x,y pairs as Base64 or Hex strings)
     * created by Shamir's Secret Sharing Algorithm requiring a minimum number of
     * share to recreate, of length shares, from the input secret raw as a string
     *
     * @param minimum int minimum
     * @param shares int shares
     * @param secret String secret
     * @param isBase64 True using encode Base64Url, otherwise encode Hex
     * @return List string shares
     * @throws Exception Input params invalid
     */
    public List create(int minimum, int shares, String secret, boolean isBase64) throws Exception {
        List rs = new ArrayList<>();
        // Verify minimum isn't greater than shares; there is no way to recreate
        // the original polynomial in our current setup, therefore it doesn't make
        // sense to generate fewer shares than are needed to reconstruct the secret.
        if (minimum <= 0 || shares <= 0) {
            throw new Exception("minimum or shares is invalid");
        }
        if (minimum > shares) {
            throw new Exception("cannot require more shares then existing");
        }
        if (secret == null || secret.isEmpty()) {
            throw new Exception("secret is NULL or empty");
        }
        
        // Convert the secret to its respective 256-bit BigInteger representation
        List secrets = splitSecretToBigInt(secret);
        
        // List of currently used numbers in the polynomial
        List numbers = new ArrayList<>();
        numbers.add(BigInteger.ZERO);
        
        // Create the polynomial of degree (minimum - 1); that is, the highest
        // order term is (minimum-1), though as there is a constant term with
        // order 0, there are (minimum) number of coefficients.
        // 
        // However, the polynomial object is a 2d array, because we are constructing
        // a different polynomial for each part of the secret
        // 
        // polynomial[parts][minimum]
        BigInteger[][] polynomial = new BigInteger[secrets.size()][minimum];
        for (int i=0; i shares, boolean isBase64) throws Exception {
        String rs = "";
        if (shares == null || shares.isEmpty()) {
            throw new Exception("shares is NULL or empty");
        }
        
        // Recreate the original object of x, y points, based upon number of shares
        // and size of each share (number of parts in the secret).
        // 
        // points[shares][parts][2]
        BigInteger[][][] points;
        if (isBase64) {
            points = decodeShareBase64(shares);
        } else {
            points = decodeShareHex(shares);
        }
        
        // Use Lagrange Polynomial Interpolation (LPI) to reconstruct the secret.
        // For each part of the secret (clearest to iterate over)...
        List secrets = new ArrayList<>();
        int numSecret = points[0].length;
        for (int j=0; j [(0-bx)/(ax-bx)] * ...
                        BigInteger bx = points[k][j][0]; // bx
                        BigInteger negbx = bx.multiply(new BigInteger("-1")); // (0-bx)
                        BigInteger axbx = ax.subtract(bx); // (ax-bx)
                        numerator = numerator.multiply(negbx).mod(PRIME); // (0-bx)*...
                        denominator = denominator.multiply(axbx).mod(PRIME); // (ax-bx)*...
                    }
                }
                
                // LPI product: x=0, y = ay * [(x-bx)/(ax-bx)] * ...
                // multiply together the points (ay)(numerator)(denominator)^-1 ...
                BigInteger fx = ay.multiply(numerator).mod(PRIME);
                fx = fx.multiply(denominator.modInverse(PRIME)).mod(PRIME);
                
                // LPI sum: s = fx + fx + ...
                BigInteger secret = secrets.get(j);
                secret = secret.add(fx).mod(PRIME);
                secrets.set(j, secret);
            }
        }
        
        // recover secret string.
        rs = mergeBigIntToString(secrets);
        return rs;
    }
    
    /**
     * Takes a string array of shares encoded in Base64 created via Shamir's
     * Algorithm; each string must be of equal length of a multiple of 88 characters
     * as a single 88 character share is a pair of 256-bit numbers (x, y).
     *
     * @param shares List string shares
     * @return BigInteger[][][] Matrix points
     * @throws Exception Input params invalid
     */
    public BigInteger[][][] decodeShareBase64(List shares) throws Exception {
        // Recreate the original object of x, y points, based upon number of shares
        // and size of each share (number of parts in the secret).
        // 
        // points[shares][parts][2]
        BigInteger[][][] points = new BigInteger[shares.size()][][];
        
        // For each share...
        for (int i=0; i shares) throws Exception {
        // Recreate the original object of x, y points, based upon number of shares
        // and size of each share (number of parts in the secret).
        // 
        // points[shares][parts][2]
        BigInteger[][][] points = new BigInteger[shares.size()][][];
        
        // For each share...
        for (int i=0; i splitSecretToBigInt(String secret) {
        List rs = new ArrayList<>();
        if (secret != null && !secret.isEmpty()) {
            byte[] sbyte = secret.getBytes(StandardCharsets.UTF_8);
            String hexData = encodeHexString(sbyte);
            int count = (int) Math.ceil(hexData.length() / 64.0);
            for (int i=0; i secrets) {
        String rs = "";
        String hexData = "";
        for (BigInteger s : secrets) {
            String tmp = s.toString(16);
            int n = 64-tmp.length();
            for (int j=0; j= 0 && bytes[i] == 0) {
            --i;
        }
        return Arrays.copyOf(bytes, i + 1);
    }
    
    // Returns a random number from the range (0, PRIME-1) inclusive
    public BigInteger random() {
        BigInteger rs = new BigInteger(256, rand);
        while (rs.compareTo(PRIME) >= 0) {
            rs = new BigInteger(256, rand);
        }
        return rs;
    }
    
    // inNumbers(array, value) returns boolean whether or not value is in array
    public boolean inNumbers(List numbers, BigInteger value) {
        for (BigInteger n : numbers) {
            if (n.compareTo(value) == 0) {
                return true;
            }
        }
        return false;
    }
    
    // Compute the polynomial value using Horner's method.
    // https://en.wikipedia.org/wiki/Horner%27s_method
    // y = a + bx + cx^2 + dx^3 = ((dx + c)x + b)x + a
    private BigInteger evaluatePolynomial(BigInteger[][] poly, int part, BigInteger x) {
        int last = poly[part].length - 1;
        BigInteger accum = poly[part][last];
        for (int i=last-1; i>=0; --i) {
            accum = accum.multiply(x).add(poly[part][i]).mod(PRIME);
        }
        return accum;
    }
    
    // Return Base64 string from BigInteger 256 bits long
    public String toBase64(BigInteger number) {
        String hexdata = number.toString(16);
        int n = 64 - hexdata.length();
        for (int i=0; i= PRIME ==> false
            if (decode.compareTo(BigInteger.ZERO) <= 0 || decode.compareTo(PRIME) >= 0) {
                return false;
            }
        }
        return true;
    }
    
    // Takes in a given string to check if it is a valid secret
    // Requirements:
    // 	 Length multiple of 128
    //	 Can decode each 64 character block as Hex
    // Returns only success/failure (bool)
    public boolean isValidShareHex(String candidate) throws Exception {
        if (candidate == null || candidate.isEmpty()) {
            return false;
        }
        if (candidate.length()%128 != 0) {
            return false;
        }
        int count = candidate.length() / 64;
        for (int i=0; i= PRIME ==> false
            if (decode.compareTo(BigInteger.ZERO) <= 0 || decode.compareTo(PRIME) >= 0) {
                return false;
            }
        }
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy