/* * Copyright 2012 b1.org * * 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 org.b1.pack.standard.common; import com.google.common.base.Preconditions; import org.b1.pack.api.common.InvalidPasswordException; import org.spongycastle.crypto.InvalidCipherTextException; import org.spongycastle.crypto.digests.SHA256Digest; import org.spongycastle.crypto.engines.AESFastEngine; import org.spongycastle.crypto.macs.HMac; import org.spongycastle.crypto.modes.GCMBlockCipher; import org.spongycastle.crypto.modes.gcm.BasicGCMMultiplier; import org.spongycastle.crypto.params.AEADParameters; import org.spongycastle.crypto.params.KeyParameter; public class VolumeCipher { public static final int MAC_BYTE_SIZE = 16; private static final byte[] BLANK_NONCE = new byte[12]; private static final byte[] HEAD_SALT = new byte[]{0x01}; private static final byte[] TAIL_SALT = new byte[]{0x02}; private final HMac hMac; private final int iterationCount; public VolumeCipher(KeyParameter key, int iterationCount) { this.iterationCount = iterationCount; hMac = new HMac(new SHA256Digest()); hMac.init(key); } public int getIterationCount() { return iterationCount; } public byte[] cipherHead(boolean encryption, byte[] in) { return doCipher(encryption, HEAD_SALT, in); } public byte[] cipherTail(boolean encryption, byte[] in) { return doCipher(encryption, TAIL_SALT, in); } public byte[] cipherBlock(boolean encryption, long blockOffset, byte[] in) { return doCipher(encryption, PackCipher.longToUtf8(blockOffset), in); } private byte[] doCipher(boolean encryption, byte[] salt, byte[] in) { GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine(), new BasicGCMMultiplier()); AEADParameters parameters = new AEADParameters(PackCipher.generateKey(hMac, salt), MAC_BYTE_SIZE * 8, BLANK_NONCE, null); cipher.init(encryption, parameters); byte[] out = new byte[cipher.getOutputSize(in.length)]; int count = cipher.processBytes(in, 0, in.length, out, 0); try { Preconditions.checkState(count + cipher.doFinal(out, count) == out.length); } catch (InvalidCipherTextException e) { throw new InvalidPasswordException("Password is invalid or archive is corrupt", e); } return out; } }