/*
* Copyright 2011 Federal Chancellery Austria and
* Graz University of Technology
*
* 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 at.gv.util;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import at.gv.util.data.BPK;
import at.gv.util.data.WBPK;
import at.gv.util.ex.InternalErrorException;
/**
* Utility class for sector specific PINs (Bereichsspezifische
* Personenkennzeichen).
*
* @author Arne Tauber
*
*/
public final class BpkUtil {
public static final String SECTOR_DELIVERY = "ZU";
public static final String PUBLIC_KEY_ZUSEKOPF_SN01_BASE64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfGFFEzWl1kSbzmk4pWVeftyD2aWTVQ8xSIwb6ECLdFTy4zE9LI6a87zlPbOOzvvdO+Nv1VCvT+WqD9HOCtPwealwwRKS2cHI9aqYyozqDOIGBRIz7MDBKuqaTwCvonFe0MUBxVWDDTmqhDIjkN0uDQidQtqqjzupEzuQ59lVpBQIDAQAB";
public static final String prefix = "urn:publicid:gv.at:cdid+";
private static String calcDigest(String data)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
byte[] digest = sha1.digest(data.getBytes("ISO-8859-1"));
return new String(Base64.encodeBase64(digest));
}
public static String calcBPK(String baseID, String sector) {
MiscUtil.assertNotEmpty(baseID, "BaseID");
MiscUtil.assertNotEmpty(sector, "Sector");
// prefix according to specification
String prefix = "urn:publicid:gv.at:cdid+";
// concatenation according to algorithm
String data;
if (sector.startsWith(prefix))
data = baseID + "+" + sector;
else
data = baseID + "+" + prefix + sector;
String hash = null;
try {
hash = calcDigest(data);
} catch (NoSuchAlgorithmException noSuchAlgorithmException) {
throw new InternalErrorException(noSuchAlgorithmException);
} catch (UnsupportedEncodingException unsupportedEncodingException) {
throw new InternalErrorException(unsupportedEncodingException);
}
return hash;
}
public static BPK createBPK(String baseID, String sector) {
if (sector.startsWith(prefix))
sector = sector.substring(prefix.length());
return new BPK(sector, calcBPK(baseID, sector));
}
public static String calcZBPK(String baseID) {
return calcBPK(baseID, SECTOR_DELIVERY);
}
public static BPK createZBPK(String baseID) {
return new BPK(SECTOR_DELIVERY, calcZBPK(baseID));
}
public static String calcWBPK(String baseID, String nameQualifier) {
MiscUtil.assertNotEmpty(baseID, "BaseID");
MiscUtil.assertNotEmpty(nameQualifier, "Name qualifier");
// concatenation according to algorithm
String data = baseID + "+" + nameQualifier;
String wBPK = null;
try {
wBPK = calcDigest(data);
} catch (NoSuchAlgorithmException e) {
throw new InternalErrorException(e);
} catch (UnsupportedEncodingException e) {
throw new InternalErrorException(e);
}
return wBPK;
}
public static WBPK createWBPK(String baseID, String nameQualifier) {
return new WBPK(calcWBPK(baseID, nameQualifier), nameQualifier);
}
public static byte[] encrypt(byte[] inputBytes, PublicKey publicKey) {
byte[] result;
try {
Cipher cipher = null;
try {
cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle
} catch(NoSuchAlgorithmException e) {
cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider
}
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
result = cipher.doFinal(inputBytes);
} catch (InvalidKeyException e) {
throw new InternalErrorException(e);
} catch (IllegalBlockSizeException e) {
throw new InternalErrorException(e);
} catch (BadPaddingException e) {
throw new InternalErrorException(e);
} catch (NoSuchAlgorithmException e) {
throw new InternalErrorException(e);
} catch (NoSuchPaddingException e) {
throw new InternalErrorException(e);
}
return result;
}
public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey) {
byte[] result;
try {
Cipher cipher = null;
try {
cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle
} catch(NoSuchAlgorithmException e) {
cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider
}
cipher.init(Cipher.DECRYPT_MODE, privateKey);
result = cipher.doFinal(encryptedBytes);
} catch (InvalidKeyException e) {
throw new InternalErrorException(e);
} catch (IllegalBlockSizeException e) {
throw new InternalErrorException(e);
} catch (BadPaddingException e) {
throw new InternalErrorException(e);
} catch (NoSuchAlgorithmException e) {
throw new InternalErrorException(e);
} catch (NoSuchPaddingException e) {
throw new InternalErrorException(e);
}
return result;
}
public static String encryptBPK(BPK bpk, PublicKey publicKey) {
MiscUtil.assertNotNull(bpk, "BPK");
MiscUtil.assertNotNull(publicKey, "publicKey");
String input = "V1::urn:publicid:gv.at:cdid+" + bpk.getSector() + "::"
+ bpk.getBpk() + "::"
+ DateTimeUtil.formatDate(new Date(), "yyyy-MM-dd'T'HH:mm:ss");
System.out.println(input);
byte[] result;
try {
byte[] inputBytes = input.getBytes("ISO-8859-1");
result = encrypt(inputBytes, publicKey);
} catch (UnsupportedEncodingException e) {
throw new InternalErrorException(e);
}
return new String(Base64.encodeBase64(result)).replaceAll("\r\n", "");
}
public static BPK decryptBPK(String encryptedBpk, PrivateKey privateKey) {
MiscUtil.assertNotEmpty(encryptedBpk, "Encrypted BPK");
MiscUtil.assertNotNull(privateKey, "Private key");
String decryptedString;
try {
byte[] encryptedBytes = Base64.decodeBase64(encryptedBpk
.getBytes("ISO-8859-1"));
byte[] decryptedBytes = decrypt(encryptedBytes, privateKey);
decryptedString = new String(decryptedBytes, "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
throw new InternalErrorException(e);
}
String tmp = decryptedString.substring(decryptedString.indexOf('+') + 1);
String sector = tmp.substring(0, tmp.indexOf("::"));
tmp = tmp.substring(tmp.indexOf("::") + 2);
String bPK = tmp.substring(0, tmp.indexOf("::"));
return new BPK(sector, bPK);
}
public static String calcVZBPK(String baseID, PublicKey publicKey) {
MiscUtil.assertNotEmpty(baseID, "BaseID");
MiscUtil.assertNotNull(publicKey, "Public key");
String bpk = calcZBPK(baseID);
String input = "Ver:1::urn:publicid:gv.at:cdid+" + SECTOR_DELIVERY + "::"
+ bpk + "::"
+ DateTimeUtil.formatDate(new Date(), "yyyy-MM-dd'T'HH:mm:ss");
byte[] result;
try {
byte[] inputBytes = input.getBytes("ISO-8859-1");
result = encrypt(inputBytes, publicKey);
} catch (UnsupportedEncodingException e) {
throw new InternalErrorException(e);
}
return new String(Base64.encodeBase64(result)).replaceAll("\r\n", "");
}
public static String calcVZBPK(String baseID) {
MiscUtil.assertNotEmpty(baseID, "BaseID");
PublicKey publicKey;
try {
byte[] publicKeyBytes = Base64
.decodeBase64(PUBLIC_KEY_ZUSEKOPF_SN01_BASE64.getBytes("ISO-8859-1"));
KeyFactory rsaKeyFac = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
publicKey = (RSAPublicKey) rsaKeyFac.generatePublic(keySpec);
} catch (UnsupportedEncodingException e) {
throw new InternalErrorException(e);
} catch (NoSuchAlgorithmException e) {
throw new InternalErrorException(e);
} catch (InvalidKeySpecException e) {
throw new InternalErrorException(e);
}
return calcVZBPK(baseID, publicKey);
}
/**
* @deprecated Use {@link BpkUtil#calcVZBPK(String, PublicKey)} instead.
*/
public static String calcVzbpk(String baseID, String sector,
byte[] rsaPublicKey) {
String zbpk = calcZBPK(baseID);
return encryptBPK(zbpk, sector, new Date(), rsaPublicKey);
}
/**
* Calculates the check digit for commercial register digits.
* A commercial register number is build by concatinating the commercial
* register digits with the check digit (e.g. 123456 and i results in
* 123456i).
*
* @param commercialRegisterDigits
* The commercial register number consisting of 6 digits.
* @return The check digit.
*/
public static char calcCheckDigitFromCommercialRegisterNumber(
String commercialRegisterDigits) {
final int[] WEIGHT = { 6, 4, 14, 15, 10, 1 };
final char[] CHECKDIGIT = { 'a', 'b', 'd', 'f', 'g', 'h', 'i', 'k', 'm',
'p', 's', 't', 'v', 'w', 'x', 'y', 'z' };
if (commercialRegisterDigits == null) {
throw new NullPointerException("Commercial register number missing.");
}
commercialRegisterDigits = StringUtils.leftPad(commercialRegisterDigits, 6,
'0');
if (!commercialRegisterDigits.matches("\\d{6}")) {
throw new IllegalArgumentException(
"Invalid commercial register number provided.");
}
int sum = 0;
for (int i = 0; i < commercialRegisterDigits.length(); i++) {
int value = commercialRegisterDigits.charAt(i) - '0';
sum += WEIGHT[i] * value;
}
return CHECKDIGIT[sum % 17];
}
/**
* Checks the validity of a commercial register number.
*
* @param commercialRegisterNumber
* The commercial register number.
* @return {@code true} if the given commercial register number is valid,
* {@code false} if not.
*/
public static boolean checkCommercialRegisterNumber(
String commercialRegisterNumber) {
if (commercialRegisterNumber == null) {
return false;
}
commercialRegisterNumber = StringUtils.leftPad(commercialRegisterNumber, 7,
'0');
if (!commercialRegisterNumber.matches("\\d{6}[abdfghikmpstvwxzy]")) {
return false;
}
String digits = commercialRegisterNumber.substring(0,
commercialRegisterNumber.length() - 1);
char checkDigit = commercialRegisterNumber.charAt(commercialRegisterNumber
.length() - 1);
boolean result = calcCheckDigitFromCommercialRegisterNumber(digits) == checkDigit;
return result;
}
/**
* @deprecated Use {@link BpkUtil#encryptBPK(BPK, PublicKey)} instead.
*/
public static String encryptBPK(String bpk, String context, Date date,
byte[] binaryPublicKey) {
String inputData = "Ver:1::urn:publicid:gv.at:cdid+" + context + "::" + bpk
+ "::" + DateTimeUtil.formatDate(date, "yyyy-MM-dd'T'HH:mm:ss");
byte[] plain = new String(inputData).getBytes();
// log.debug("inputData = " + inputData);
byte[] encrypted = null;
try {
KeyFactory rsaKeyFac = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(binaryPublicKey);
PublicKey rsaPublicKey = (RSAPublicKey) rsaKeyFac.generatePublic(keySpec);
Cipher rsa = null;
try {
rsa = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle
} catch(NoSuchAlgorithmException e) {
rsa = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider
}
rsa.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
encrypted = rsa.doFinal(plain);
} catch (InvalidKeyException e) {
throw new InternalErrorException(e);
} catch (NoSuchAlgorithmException e) {
throw new InternalErrorException(e);
} catch (NoSuchPaddingException e) {
throw new InternalErrorException(e);
} catch (IllegalArgumentException e) {
throw new InternalErrorException(e);
} catch (IllegalBlockSizeException e) {
throw new InternalErrorException(e);
} catch (BadPaddingException e) {
throw new InternalErrorException(e);
} catch (InvalidKeySpecException e) {
throw new InternalErrorException(e);
}
return new String(Base64.encodeBase64(encrypted)).replaceAll("\r\n", "");
}
private BpkUtil() {
}
}