001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.security; 020 021import org.bouncycastle.jce.provider.BouncyCastleProvider; 022import org.jpos.iso.ISOUtil; 023 024import javax.crypto.*; 025import javax.crypto.spec.IvParameterSpec; 026import java.lang.ref.Cleaner; 027import java.nio.ByteBuffer; 028import java.security.*; 029import java.util.Arrays; 030import java.util.Random; 031import java.util.function.Supplier; 032 033public class SensitiveString implements Supplier<String>, AutoCloseable { 034 private SecretKey key; 035 private byte[] encoded; 036 private static Random rnd; 037 private static final String AES = "AES/GCM/NoPadding"; 038 039 private static final Cleaner cleaner = Cleaner.create(); 040 private Cleaner.Cleanable cleanable; 041 042 static { 043 rnd = new SecureRandom(); 044 if(Security.getProvider("BC") == null) 045 Security.addProvider(new BouncyCastleProvider()); 046 } 047 048 public SensitiveString(String s) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException, InvalidAlgorithmParameterException { 049 key = generateKey(); 050 encoded = encrypt(s.getBytes()); 051 cleanable = cleaner.register(this, this::clean); 052 } 053 054 @Override 055 public boolean equals(Object o) { 056 if (this == o) return true; 057 if (o == null || getClass() != o.getClass()) return false; 058 SensitiveString that = (SensitiveString) o; 059 return this.get().equals(that.get()); 060 } 061 062 private SecretKey generateKey() throws NoSuchAlgorithmException { 063 KeyGenerator keyGen = KeyGenerator.getInstance("AES"); 064 int maxKeyLength = Cipher.getMaxAllowedKeyLength(keyGen.getAlgorithm()); 065 keyGen.init(maxKeyLength == Integer.MAX_VALUE ? 256 : maxKeyLength); 066 return keyGen.generateKey(); 067 } 068 069 private byte[] encrypt(byte[] b) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { 070 final Cipher cipher = Cipher.getInstance(AES); 071 final byte[] iv = randomIV(); 072 cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); 073 byte[] enc = cipher.doFinal(b); 074 ByteBuffer buf = ByteBuffer.allocate(iv.length + enc.length); 075 buf.put(ISOUtil.xor(iv, SystemSeed.getSeed(iv.length, iv.length))); 076 buf.put(enc); 077 return buf.array(); 078 } 079 private byte[] decrypt(byte[] encoded) 080 throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, 081 IllegalBlockSizeException, NoSuchProviderException, InvalidAlgorithmParameterException 082 { 083 byte[] iv = new byte[16]; 084 byte[] cryptogram = new byte[encoded.length - iv.length]; 085 System.arraycopy(encoded, 0, iv, 0, iv.length); 086 System.arraycopy(encoded, iv.length, cryptogram, 0, cryptogram.length); 087 final Cipher cipher = Cipher.getInstance(AES); 088 cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ISOUtil.xor(iv, SystemSeed.getSeed(iv.length, iv.length)))); 089 return cipher.doFinal(cryptogram); 090 } 091 092 private byte[] randomIV() { 093 final byte[] b = new byte[16]; 094 rnd.nextBytes(b); 095 return b; 096 } 097 098 public void clean () { 099 byte[] b = encoded; 100 encoded = null; 101 Arrays.fill (b, (byte) 0); 102 } 103 104 @Override 105 public String get() { 106 if (encoded == null) 107 throw new IllegalStateException ("SensitiveString not available"); 108 try { 109 return new String(decrypt(encoded)); 110 } catch (NoSuchPaddingException | NoSuchAlgorithmException | BadPaddingException | InvalidKeyException | NoSuchProviderException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) { 111 throw new AssertionError(e.getMessage()); 112 } 113 } 114 115 @Override 116 public void close() throws Exception { 117 clean(); 118 } 119}