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