|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.rsa; |
|
|
|
import java.util.*; |
|
|
|
import java.security.*; |
|
import java.security.spec.*; |
|
|
|
import javax.crypto.BadPaddingException; |
|
import javax.crypto.spec.PSource; |
|
import javax.crypto.spec.OAEPParameterSpec; |
|
|
|
import sun.security.jca.JCAUtil; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final class RSAPadding { |
|
|
|
// NOTE: the constants below are embedded in the JCE RSACipher class |
|
// file. Do not change without coordinating the update |
|
|
|
|
|
public static final int PAD_BLOCKTYPE_1 = 1; |
|
|
|
public static final int PAD_BLOCKTYPE_2 = 2; |
|
|
|
public static final int PAD_NONE = 3; |
|
|
|
public static final int PAD_OAEP_MGF1 = 4; |
|
|
|
|
|
private final int type; |
|
|
|
|
|
private final int paddedSize; |
|
|
|
|
|
private SecureRandom random; |
|
|
|
|
|
private final int maxDataSize; |
|
|
|
|
|
private MessageDigest md; |
|
|
|
|
|
private MGF1 mgf; |
|
|
|
|
|
private byte[] lHash; |
|
|
|
|
|
|
|
|
|
*/ |
|
public static RSAPadding getInstance(int type, int paddedSize) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
return new RSAPadding(type, paddedSize, null, null); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static RSAPadding getInstance(int type, int paddedSize, |
|
SecureRandom random) throws InvalidKeyException, |
|
InvalidAlgorithmParameterException { |
|
return new RSAPadding(type, paddedSize, random, null); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static RSAPadding getInstance(int type, int paddedSize, |
|
SecureRandom random, OAEPParameterSpec spec) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
return new RSAPadding(type, paddedSize, random, spec); |
|
} |
|
|
|
|
|
private RSAPadding(int type, int paddedSize, SecureRandom random, |
|
OAEPParameterSpec spec) throws InvalidKeyException, |
|
InvalidAlgorithmParameterException { |
|
this.type = type; |
|
this.paddedSize = paddedSize; |
|
this.random = random; |
|
if (paddedSize < 64) { |
|
|
|
throw new InvalidKeyException("Padded size must be at least 64"); |
|
} |
|
switch (type) { |
|
case PAD_BLOCKTYPE_1: |
|
case PAD_BLOCKTYPE_2: |
|
maxDataSize = paddedSize - 11; |
|
break; |
|
case PAD_NONE: |
|
maxDataSize = paddedSize; |
|
break; |
|
case PAD_OAEP_MGF1: |
|
String mdName = "SHA-1"; |
|
String mgfMdName = mdName; |
|
byte[] digestInput = null; |
|
try { |
|
if (spec != null) { |
|
mdName = spec.getDigestAlgorithm(); |
|
String mgfName = spec.getMGFAlgorithm(); |
|
if (!mgfName.equalsIgnoreCase("MGF1")) { |
|
throw new InvalidAlgorithmParameterException |
|
("Unsupported MGF algo: " + mgfName); |
|
} |
|
mgfMdName = ((MGF1ParameterSpec)spec.getMGFParameters()) |
|
.getDigestAlgorithm(); |
|
PSource pSrc = spec.getPSource(); |
|
String pSrcAlgo = pSrc.getAlgorithm(); |
|
if (!pSrcAlgo.equalsIgnoreCase("PSpecified")) { |
|
throw new InvalidAlgorithmParameterException |
|
("Unsupported pSource algo: " + pSrcAlgo); |
|
} |
|
digestInput = ((PSource.PSpecified) pSrc).getValue(); |
|
} |
|
md = MessageDigest.getInstance(mdName); |
|
mgf = new MGF1(mgfMdName); |
|
} catch (NoSuchAlgorithmException e) { |
|
throw new InvalidKeyException("Digest not available", e); |
|
} |
|
lHash = getInitialHash(md, digestInput); |
|
int digestLen = lHash.length; |
|
maxDataSize = paddedSize - 2 - 2 * digestLen; |
|
if (maxDataSize <= 0) { |
|
throw new InvalidKeyException |
|
("Key is too short for encryption using OAEPPadding" + |
|
" with " + mdName + " and " + mgf.getName()); |
|
} |
|
break; |
|
default: |
|
throw new InvalidKeyException("Invalid padding: " + type); |
|
} |
|
} |
|
|
|
|
|
private static final Map<String,byte[]> emptyHashes = |
|
Collections.synchronizedMap(new HashMap<String,byte[]>()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static byte[] getInitialHash(MessageDigest md, |
|
byte[] digestInput) { |
|
byte[] result; |
|
if ((digestInput == null) || (digestInput.length == 0)) { |
|
String digestName = md.getAlgorithm(); |
|
result = emptyHashes.get(digestName); |
|
if (result == null) { |
|
result = md.digest(); |
|
emptyHashes.put(digestName, result); |
|
} |
|
} else { |
|
result = md.digest(digestInput); |
|
} |
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public int getMaxDataSize() { |
|
return maxDataSize; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public byte[] pad(byte[] data) throws BadPaddingException { |
|
return pad(data, 0, data.length); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public byte[] pad(byte[] data, int ofs, int len) |
|
throws BadPaddingException { |
|
if (len > maxDataSize) { |
|
throw new BadPaddingException("Data must be shorter than " |
|
+ (maxDataSize + 1) + " bytes but received " |
|
+ len + " bytes."); |
|
} |
|
switch (type) { |
|
case PAD_NONE: |
|
return RSACore.convert(data, ofs, len); |
|
case PAD_BLOCKTYPE_1: |
|
case PAD_BLOCKTYPE_2: |
|
return padV15(data, ofs, len); |
|
case PAD_OAEP_MGF1: |
|
return padOAEP(data, ofs, len); |
|
default: |
|
throw new AssertionError(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public byte[] unpad(byte[] padded) throws BadPaddingException { |
|
if (padded.length != paddedSize) { |
|
throw new BadPaddingException("Decryption error." + |
|
"The padded array length (" + padded.length + |
|
") is not the specified padded size (" + paddedSize + ")"); |
|
} |
|
switch (type) { |
|
case PAD_NONE: |
|
return padded; |
|
case PAD_BLOCKTYPE_1: |
|
case PAD_BLOCKTYPE_2: |
|
return unpadV15(padded); |
|
case PAD_OAEP_MGF1: |
|
return unpadOAEP(padded); |
|
default: |
|
throw new AssertionError(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private byte[] padV15(byte[] data, int ofs, int len) throws BadPaddingException { |
|
byte[] padded = new byte[paddedSize]; |
|
System.arraycopy(data, ofs, padded, paddedSize - len, len); |
|
int psSize = paddedSize - 3 - len; |
|
int k = 0; |
|
padded[k++] = 0; |
|
padded[k++] = (byte)type; |
|
if (type == PAD_BLOCKTYPE_1) { |
|
|
|
while (psSize-- > 0) { |
|
padded[k++] = (byte)0xff; |
|
} |
|
} else { |
|
|
|
if (random == null) { |
|
random = JCAUtil.getSecureRandom(); |
|
} |
|
// generate non-zero padding bytes |
|
|
|
while (psSize > 0) { |
|
// extra bytes to avoid zero bytes, |
|
|
|
byte[] r = new byte[psSize + 4]; |
|
random.nextBytes(r); |
|
for (int i = 0; i < r.length && psSize > 0; i++) { |
|
if (r[i] != 0) { |
|
padded[k++] = r[i]; |
|
psSize--; |
|
} |
|
} |
|
} |
|
} |
|
return padded; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] unpadV15(byte[] padded) throws BadPaddingException { |
|
int k = 0; |
|
boolean bp = false; |
|
|
|
if (padded[k++] != 0) { |
|
bp = true; |
|
} |
|
if (padded[k++] != type) { |
|
bp = true; |
|
} |
|
int p = 0; |
|
while (k < padded.length) { |
|
int b = padded[k++] & 0xff; |
|
if ((b == 0) && (p == 0)) { |
|
p = k; |
|
} |
|
if ((k == padded.length) && (p == 0)) { |
|
bp = true; |
|
} |
|
if ((type == PAD_BLOCKTYPE_1) && (b != 0xff) && |
|
(p == 0)) { |
|
bp = true; |
|
} |
|
} |
|
int n = padded.length - p; |
|
if (n > maxDataSize) { |
|
bp = true; |
|
} |
|
|
|
|
|
byte[] padding = new byte[p]; |
|
System.arraycopy(padded, 0, padding, 0, p); |
|
|
|
byte[] data = new byte[n]; |
|
System.arraycopy(padded, p, data, 0, n); |
|
|
|
BadPaddingException bpe = new BadPaddingException("Decryption error"); |
|
|
|
if (bp) { |
|
throw bpe; |
|
} else { |
|
return data; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] padOAEP(byte[] M, int ofs, int len) throws BadPaddingException { |
|
if (random == null) { |
|
random = JCAUtil.getSecureRandom(); |
|
} |
|
int hLen = lHash.length; |
|
|
|
// 2.d: generate a random octet string seed of length hLen |
|
|
|
byte[] seed = new byte[hLen]; |
|
random.nextBytes(seed); |
|
|
|
|
|
byte[] EM = new byte[paddedSize]; |
|
|
|
|
|
int seedStart = 1; |
|
int seedLen = hLen; |
|
|
|
|
|
System.arraycopy(seed, 0, EM, seedStart, seedLen); |
|
|
|
// start and length of data block DB in EM |
|
|
|
int dbStart = hLen + 1; |
|
int dbLen = EM.length - dbStart; |
|
|
|
|
|
int mStart = paddedSize - len; |
|
|
|
// build DB |
|
// 2.b: Concatenate lHash, PS, a single octet with hexadecimal value |
|
// 0x01, and the message M to form a data block DB of length |
|
// k - hLen -1 octets as DB = lHash || PS || 0x01 || M |
|
|
|
System.arraycopy(lHash, 0, EM, dbStart, hLen); |
|
EM[mStart - 1] = 1; |
|
System.arraycopy(M, ofs, EM, mStart, len); |
|
|
|
|
|
mgf.generateAndXor(EM, seedStart, seedLen, dbLen, EM, dbStart); |
|
|
|
|
|
mgf.generateAndXor(EM, dbStart, dbLen, seedLen, EM, seedStart); |
|
|
|
return EM; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private byte[] unpadOAEP(byte[] padded) throws BadPaddingException { |
|
byte[] EM = padded; |
|
boolean bp = false; |
|
int hLen = lHash.length; |
|
|
|
if (EM[0] != 0) { |
|
bp = true; |
|
} |
|
|
|
int seedStart = 1; |
|
int seedLen = hLen; |
|
|
|
int dbStart = hLen + 1; |
|
int dbLen = EM.length - dbStart; |
|
|
|
mgf.generateAndXor(EM, dbStart, dbLen, seedLen, EM, seedStart); |
|
mgf.generateAndXor(EM, seedStart, seedLen, dbLen, EM, dbStart); |
|
|
|
|
|
for (int i = 0; i < hLen; i++) { |
|
if (lHash[i] != EM[dbStart + i]) { |
|
bp = true; |
|
} |
|
} |
|
|
|
int padStart = dbStart + hLen; |
|
int onePos = -1; |
|
|
|
for (int i = padStart; i < EM.length; i++) { |
|
int value = EM[i]; |
|
if (onePos == -1) { |
|
if (value == 0x00) { |
|
// continue; |
|
} else if (value == 0x01) { |
|
onePos = i; |
|
} else { |
|
bp = true; |
|
} |
|
} |
|
} |
|
|
|
|
|
if (onePos == -1) { |
|
bp = true; |
|
onePos = EM.length - 1; |
|
} |
|
|
|
int mStart = onePos + 1; |
|
|
|
|
|
byte [] tmp = new byte[mStart - padStart]; |
|
System.arraycopy(EM, padStart, tmp, 0, tmp.length); |
|
|
|
byte [] m = new byte[EM.length - mStart]; |
|
System.arraycopy(EM, mStart, m, 0, m.length); |
|
|
|
BadPaddingException bpe = new BadPaddingException("Decryption error"); |
|
|
|
if (bp) { |
|
throw bpe; |
|
} else { |
|
return m; |
|
} |
|
} |
|
} |