/* * 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() { } }