001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (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 General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.crypto;
019
020import java.io.InputStream;
021import java.io.OutputStream;
022import java.util.regex.Pattern;
023
024/**
025 * A cipher spec represents the definition of a cipher/encryption algorithm and the
026 * corresponding settings required to instantiate a new cipher object.
027 *
028 * <p>Cipher specs are identified by an identifier (<i>id</i>), which will (when the
029 * cipher spec is used by the {@link MultiCipherOutputStream}) be written to the output
030 * file format. When the file is read by {@link MultiCipherInputStream}, the identifier
031 * is looked up using the {@link CipherSpecs} class.
032 *
033 * <p>While it would be technically possible to define any kind of cipher using this class,
034 * this class restricts the allowed algorithms to a few ones that are considered secure.
035 *
036 * <p>Instantiating a cipher spec that does pass the sanity checks will result in a
037 * RuntimeException.
038 *
039 * @author Philipp C. Heckel (philipp.heckel@gmail.com)
040 */
041public abstract class CipherSpec {
042        public static final Pattern ALLOWED_CIPHER_ALGORITHMS = Pattern.compile("^HmacSHA256$|(^(AES|Twofish)/(GCM|EAX)/.+)");
043
044        private int id;
045        private String algorithm;
046        private int keySize; // in bits
047        private int ivSize; // in bits
048        private boolean needsUnlimitedStrength;
049
050        public CipherSpec(int id, String algorithm, int keySize, int ivSize, boolean needsUnlimitedStrength) {
051                this.id = id;
052                this.algorithm = algorithm;
053                this.keySize = keySize;
054                this.ivSize = ivSize;
055                this.needsUnlimitedStrength = needsUnlimitedStrength;
056
057                doSanityChecks();
058        }
059
060        public int getId() {
061                return id;
062        }
063
064        public boolean needsUnlimitedStrength() {
065                return needsUnlimitedStrength;
066        }
067
068        public String getAlgorithm() {
069                return algorithm;
070        }
071
072        public int getKeySize() {
073                return keySize;
074        }
075
076        public int getIvSize() {
077                return ivSize;
078        }
079
080        public abstract OutputStream newCipherOutputStream(OutputStream underlyingOutputStream, byte[] secretKey, byte[] iv) throws CipherException;
081
082        public abstract InputStream newCipherInputStream(InputStream underlyingInputStream, byte[] secretKey, byte[] iv) throws CipherException;
083
084        @Override
085        public String toString() {
086                return algorithm + ", " + keySize + " bit";
087        }
088
089        private void doSanityChecks() {
090                if (!ALLOWED_CIPHER_ALGORITHMS.matcher(algorithm).matches()) {
091                        throw new RuntimeException("Cipher algorithm or mode not allowed: " + algorithm + ". This mode is not considered secure.");
092                }
093        }
094
095        @Override
096        public int hashCode() {
097                final int prime = 31;
098                int result = 1;
099                result = prime * result + ((algorithm == null) ? 0 : algorithm.hashCode());
100                result = prime * result + id;
101                result = prime * result + ivSize;
102                result = prime * result + keySize;
103                result = prime * result + (needsUnlimitedStrength ? 1231 : 1237);
104                return result;
105        }
106
107        @Override
108        public boolean equals(Object obj) {
109                if (this == obj) {
110                        return true;
111                }
112                if (obj == null) {
113                        return false;
114                }
115                if (!(obj instanceof CipherSpec)) {
116                        return false;
117                }
118                CipherSpec other = (CipherSpec) obj;
119                if (algorithm == null) {
120                        if (other.algorithm != null) {
121                                return false;
122                        }
123                }
124                else if (!algorithm.equals(other.algorithm)) {
125                        return false;
126                }
127                if (id != other.id) {
128                        return false;
129                }
130                if (ivSize != other.ivSize) {
131                        return false;
132                }
133                if (keySize != other.keySize) {
134                        return false;
135                }
136                if (needsUnlimitedStrength != other.needsUnlimitedStrength) {
137                        return false;
138                }
139                return true;
140        }
141}