001package co.codewizards.cloudstore.core.otp;
002
003import static co.codewizards.cloudstore.core.io.StreamUtil.*;
004import static co.codewizards.cloudstore.core.oio.OioFileFactory.*;
005
006import java.io.IOException;
007import java.io.OutputStream;
008import java.nio.ByteBuffer;
009import java.nio.CharBuffer;
010import java.nio.charset.StandardCharsets;
011import java.util.Arrays;
012
013import co.codewizards.cloudstore.core.config.ConfigDir;
014import co.codewizards.cloudstore.core.oio.File;
015import co.codewizards.cloudstore.core.otp.OneTimePadEncryptor.Result;
016import co.codewizards.cloudstore.core.util.IOUtil;
017
018/**
019 * Registry for passwords that need to be encrypted with OTP technique.
020 * You can, using methods of this class, encrypt with OTP and store a password
021 *  in a file in ConfigDir, and later retrieve from the file and decrypt it.
022 * Encrypted password is stored in a file named fileNamePrefix + PASSWORD_FILE_SUFFIX
023 * Random key in a file named fileNamePrefix + RANDOM_KEY_FILE_SUFFIX
024 *
025 * @author Wojtek Wilk - wilk.wojtek at gmail.com
026 */
027public class OneTimePadRegistry {
028
029        public static final String PASSWORD_FILE_SUFFIX = "Password";
030        public static final String RANDOM_KEY_FILE_SUFFIX = "RandomKey";
031
032        private final String fileNamePrefix;
033        private final OneTimePadEncryptor encryptor = new OneTimePadEncryptor();
034
035        public OneTimePadRegistry(final String fileNamePrefix){
036                this.fileNamePrefix = fileNamePrefix;
037        }
038
039        public void encryptAndStorePassword(final char[] password){
040                final Result result = encryptor.encrypt(toBytes(password));
041                try {
042                        writeToFile(result.getEncryptedMessage(), PASSWORD_FILE_SUFFIX);
043                        writeToFile(result.getRandomKey(), RANDOM_KEY_FILE_SUFFIX);
044                } catch (IOException e) {
045                        throw new RuntimeException(e);
046                }
047        }
048
049        public char[] readFromFileAndDecrypt(){
050                try {
051                        final byte[] encryptedPassword = readFromFile(PASSWORD_FILE_SUFFIX);
052                        final byte[] randomKey = readFromFile(RANDOM_KEY_FILE_SUFFIX);
053                        final byte[] decryptedPassword = encryptor.decrypt(encryptedPassword, randomKey);
054                        return toChars(decryptedPassword);
055                } catch (IOException e) {
056                        throw new RuntimeException(e);
057                }
058        }
059
060        private byte[] readFromFile(String fileNameSuffix) throws IOException {
061                final String fileName = fileNamePrefix + fileNameSuffix;
062                final File file =  createFile(ConfigDir.getInstance().getFile(), fileName);
063                return IOUtil.getBytesFromFile(file);
064        }
065
066        private void writeToFile(byte[] bytes, String fileNameSuffix) throws IOException{
067                final File file = createFile(ConfigDir.getInstance().getFile(), fileNamePrefix + fileNameSuffix);
068                try(final OutputStream os = castStream(file.createOutputStream())) {
069                        os.write(bytes);
070                }
071        }
072
073        /**
074         * based on @link <a href="http://stackoverflow.com/questions/5513144/converting-char-to-byte#answer-9670279">this answer</a>
075         */
076        private byte[] toBytes(final char[] chars) {
077            final CharBuffer charBuffer = CharBuffer.wrap(chars);
078            final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
079            final byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
080                    byteBuffer.position(), byteBuffer.limit());
081            clearSensitiveData(charBuffer.array(), byteBuffer.array());
082            return bytes;
083        }
084
085        private char[] toChars(final byte[] bytes){
086                final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
087                final CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);
088                final char[] chars = Arrays.copyOfRange(charBuffer.array(),
089                                charBuffer.position(), charBuffer.limit());
090                clearSensitiveData(charBuffer.array(), byteBuffer.array());
091            return chars;
092        }
093
094        private void clearSensitiveData(final char[] chars, final byte[] bytes){
095                Arrays.fill(chars, '\u0000');
096                Arrays.fill(bytes, (byte) 0);
097        }
098}