1 package org.readium.r2_streamer.parser;
3 import org.readium.r2_streamer.model.publication.Encryption;
5 import java.io.ByteArrayInputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.UnsupportedEncodingException;
9 import java.security.MessageDigest;
10 import java.security.NoSuchAlgorithmException;
11 import java.util.Formatter;
12 import java.util.HashMap;
15 * Created by gautam chibde on 18/5/17.
18 public final class EncryptionDecoder {
19 private final static String TAG = EncryptionDecoder.class.getName();
21 private static HashMap<String, Integer> decoders = new HashMap<>();
24 EncryptionDecoder.decoders.put("http://www.idpf.org/2008/embedding", 1040);
25 EncryptionDecoder.decoders.put("http://ns.adobe.com/pdf/enc#RC", 1024);
29 * Decode obfuscated font from a InputStream, if the encryption is known.
31 * @param identifier The associated publication Identifier.
32 * @param inStream font input stream
33 * @param encryption {@link Encryption} object contains encryption type
34 * @return The InputStream containing the unencrypted resource.
36 public static InputStream decode(String identifier, InputStream inStream, Encryption encryption) {
37 if (identifier == null) {
38 System.out.println(TAG + " Couldn't get the publication identifier.");
42 if (!decoders.containsKey(encryption.getAlgorithm())) {
43 System.out.println(TAG + " " + encryption.getProfile() + " is encrypted but decoder cant handle it");
46 return decodeFont(identifier, inStream, decoders.get(encryption.getAlgorithm()), encryption.getAlgorithm());
50 * Decode the given inputStream first X characters, depending of the obfuscation type.
52 * @param identifier The associated publication Identifier.
53 * @param inStream The input stream containing the data of an obfuscated font
54 * @param length The ObfuscationLength depending of the obfuscation type.
55 * @param algorithm type of algorithm
56 * @return The Deobfuscated InputStream.
58 private static InputStream decodeFont(String identifier, InputStream inStream, Integer length, String algorithm) {
60 byte[] publicationKey = null;
62 case "http://ns.adobe.com/pdf/enc#RC":
63 publicationKey = getHashKeyAdobe(identifier);
65 case "http://www.idpf.org/2008/embedding":
66 publicationKey = getHashKeyIdpf(identifier);
69 return deobfuscate(inStream, publicationKey, length);
73 * Receive an obfuscated InputStream and return deabfuscated InputStream
75 * @param inStream The input stream containing the data of an obfuscated font
76 * @param publicationKey The publicationKey used to decode the X first characters.
77 * @param length The number of characters obfuscated at the first of the file.
78 * @return The Deobfuscated InputStream.
80 private static InputStream deobfuscate(InputStream inStream, byte[] publicationKey, Integer length) {
81 if (publicationKey == null) {
85 byte[] bytes = new byte[inStream.available()];
86 int size = inStream.read(bytes);
87 int count = size > length ? length : size;
88 int pubKeyLength = publicationKey.length;
91 bytes[i] = (byte) (bytes[i] ^ (publicationKey[i % pubKeyLength]));
94 return new ByteArrayInputStream(bytes);
95 } catch (IOException e) {
96 System.out.println(TAG + ".deobfuscate() " + e);
103 * Create an EPUB font obfuscation key from one or more strings according to the rules
104 * defined in the EPUB 3 spec, 4.3 Generating the Obfuscation Key
105 * (http://www.idpf.org/epub/30/spec/epub30-ocf.html#fobfus-keygen)
107 * Squeezes out any whitespace in each UID and then concatenates the result
108 * using single space characters as the separator.
110 * @param identifier The string to convert into a key.
111 * @return obfuscation key string
113 private static byte[] getHashKeyIdpf(String identifier) {
115 MessageDigest crypt = MessageDigest.getInstance("SHA-1");
117 crypt.update(identifier.getBytes("UTF-8"));
118 return hexToBytes(byteToHex(crypt.digest()));
119 } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
125 * Generate the Hashkey used to salt the 1024 starting character of the Adobe font files.
127 * @param pubId The publication Identifier.
128 * @return The key's bytes array.
130 private static byte[] getHashKeyAdobe(String pubId) {
131 String cleanPubId = pubId.replaceAll("urn:uuid", "");
132 cleanPubId = cleanPubId.replaceAll("-", "");
133 cleanPubId = cleanPubId.replaceAll(":","");
134 return hexToBytes(cleanPubId);
138 * Convert hexadecimal String to Bytes (UInt8) array.
140 * @param hexa The hexadecimal String
141 * @return The key's bytes array.
143 private static byte[] hexToBytes(String hexa) {
144 char[] hex = hexa.toCharArray();
145 int length = hex.length / 2;
146 byte[] raw = new byte[length];
147 for (int i = 0; i < length; i++) {
148 int high = Character.digit(hex[i * 2], 16);
149 int low = Character.digit(hex[i * 2 + 1], 16);
150 int value = (high << 4) | low;
153 raw[i] = (byte) value;
159 * Convert Bytes array to hexadecimal String.
161 * @param hash bytes array.
162 * @return The hexadecimal String.
164 private static String byteToHex(byte[] hash) {
165 Formatter formatter = new Formatter();
166 for (byte b : hash) {
167 formatter.format("%02x", b);
169 String result = formatter.toString();