/* * Copyright (C) 2016 The Android Open Source Project * * 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 android.util; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.pm.Signature; import android.text.TextUtils; import libcore.util.HexEncoding; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** * Helper functions applicable to packages. * @hide */ public final class PackageUtils { private static final int LOW_RAM_BUFFER_SIZE_BYTES = 1 * 1000; // 1 kB private static final int HIGH_RAM_BUFFER_SIZE_BYTES = 1 * 1000 * 1000; // 1 MB private PackageUtils() { /* hide constructor */ } /** * @see #computeSignaturesSha256Digests(Signature[], String) */ public static @NonNull String[] computeSignaturesSha256Digests( @NonNull Signature[] signatures) { return computeSignaturesSha256Digests(signatures, null); } /** * Computes the SHA256 digests of a list of signatures. Items in the * resulting array of hashes correspond to the signatures in the * input array. * @param signatures The signatures. * @param separator Separator between each pair of characters, such as a colon, or null to omit. * @return The digest array. */ public static @NonNull String[] computeSignaturesSha256Digests( @NonNull Signature[] signatures, @Nullable String separator) { final int signatureCount = signatures.length; final String[] digests = new String[signatureCount]; for (int i = 0; i < signatureCount; i++) { digests[i] = computeSha256Digest(signatures[i].toByteArray(), separator); } return digests; } /** * Computes a SHA256 digest of the signatures' SHA256 digests. First, * individual hashes for each signature is derived in a hexademical * form, then these strings are sorted based the natural ordering, and * finally a hash is derived from these strings' bytes. * @param signatures The signatures. * @return The digest. */ public static @NonNull String computeSignaturesSha256Digest( @NonNull Signature[] signatures) { // Shortcut for optimization - most apps singed by a single cert if (signatures.length == 1) { return computeSha256Digest(signatures[0].toByteArray(), null); } // Make sure these are sorted to handle reversed certificates final String[] sha256Digests = computeSignaturesSha256Digests(signatures, null); return computeSignaturesSha256Digest(sha256Digests); } /** * Computes a SHA256 digest in of the signatures SHA256 digests. First, * the strings are sorted based the natural ordering, and then a hash is * derived from these strings' bytes. * @param sha256Digests Signature SHA256 hashes in hexademical form. * @return The digest. */ public static @NonNull String computeSignaturesSha256Digest( @NonNull String[] sha256Digests) { // Shortcut for optimization - most apps singed by a single cert if (sha256Digests.length == 1) { return sha256Digests[0]; } // Make sure these are sorted to handle reversed certificates Arrays.sort(sha256Digests); final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); for (String sha256Digest : sha256Digests) { try { bytes.write(sha256Digest.getBytes()); } catch (IOException e) { /* ignore - can't happen */ } } return computeSha256Digest(bytes.toByteArray(), null); } /** * Computes the SHA256 digest of some data. * @param data The data. * @return The digest or null if an error occurs. */ public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) { MessageDigest messageDigest; try { messageDigest = MessageDigest.getInstance("SHA256"); } catch (NoSuchAlgorithmException e) { /* can't happen */ return null; } messageDigest.update(data); return messageDigest.digest(); } /** * @see #computeSha256Digest(byte[], String) */ public static @Nullable String computeSha256Digest(@NonNull byte[] data) { return computeSha256Digest(data, null); } /** * Computes the SHA256 digest of some data. * @param data The data. * @param separator Separator between each pair of characters, such as a colon, or null to omit. * @return The digest or null if an error occurs. */ public static @Nullable String computeSha256Digest(@NonNull byte[] data, @Nullable String separator) { byte[] sha256DigestBytes = computeSha256DigestBytes(data); if (sha256DigestBytes == null) { return null; } if (separator == null) { return HexEncoding.encodeToString(sha256DigestBytes, true /* uppercase */); } int length = sha256DigestBytes.length; String[] pieces = new String[length]; for (int index = 0; index < length; index++) { pieces[index] = HexEncoding.encodeToString(sha256DigestBytes[index], true); } return TextUtils.join(separator, pieces); } /** * Creates a fixed size buffer based on whether the device is low ram or not. This is to be used * with the {@link #computeSha256DigestForLargeFile(String, byte[])} and * {@link #computeSha256DigestForLargeFile(String, byte[], String)} methods. * @return a byte array of size {@link #LOW_RAM_BUFFER_SIZE_BYTES} if the device is a low RAM * device, otherwise a byte array of size {@link #HIGH_RAM_BUFFER_SIZE_BYTES} */ public static @NonNull byte[] createLargeFileBuffer() { int bufferSize = ActivityManager.isLowRamDeviceStatic() ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES; return new byte[bufferSize]; } /** * Computes the SHA256 digest of a large file. * @param filePath The path to which the file's content is to be hashed. * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to * make use of the {@link #createLargeFileBuffer()} method to create this * buffer. * @return The byte array of SHA256 digest or null if an error occurs. */ public static @Nullable byte[] computeSha256DigestForLargeFileAsBytes(@NonNull String filePath, @NonNull byte[] fileBuffer) { MessageDigest messageDigest; try { messageDigest = MessageDigest.getInstance("SHA256"); messageDigest.reset(); } catch (NoSuchAlgorithmException e) { // this really shouldn't happen! return null; } File f = new File(filePath); try (DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f), messageDigest)) { while (digestInputStream.read(fileBuffer) != -1); } catch (IOException e) { e.printStackTrace(); return null; } return messageDigest.digest(); } /** * @see #computeSha256DigestForLargeFile(String, byte[], String) */ public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, @NonNull byte[] fileBuffer) { return computeSha256DigestForLargeFile(filePath, fileBuffer, null); } /** * Computes the SHA256 digest of a large file. * @param filePath The path to which the file's content is to be hashed. * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to * make use of the {@link #createLargeFileBuffer()} method to create this * buffer. * @param separator Separator between each pair of characters, such as colon, or null to omit. * @see #computeSha256DigestForLargeFile(String, byte[]) * @return The encoded string of SHA256 digest or null if an error occurs. */ public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, @NonNull byte[] fileBuffer, @Nullable String separator) { byte[] resultBytes = computeSha256DigestForLargeFileAsBytes(filePath, fileBuffer); if (separator == null) { return HexEncoding.encodeToString(resultBytes, false); } int length = resultBytes.length; String[] pieces = new String[length]; for (int index = 0; index < length; index++) { pieces[index] = HexEncoding.encodeToString(resultBytes[index], true); } return TextUtils.join(separator, pieces); } }