blob: 53706ccbabaad85d1c2bff48dd4c2837f47fab07 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.harmony.luni.util;
import java.io.OutputStream;
/**
* This class implements the Secure Hash Algorithm, SHA-1. The specification can
* be found at http://csrc.ncsl.nist.gov/fips/fip180-1.txt
*/
public class SHAOutputStream extends OutputStream implements Cloneable {
/* Constants as in the specification */
// K in iterations 0..19, from spec
private static final int K0_19 = 0x5a827999;
// K in iterations 20..39, from spec
private static final int K20_39 = 0x6ed9eba1;
// K in iterations 40..59, from spec
private static final int K40_59 = 0x8f1bbcdc;
// K in iterations 60..79, from spec
private static final int K60_79 = 0xca62c1d6;
// H0, from spec
private static final int H0 = 0x67452301;
// H1, from spec
private static final int H1 = 0xefcdab89;
// H2, from spec
private static final int H2 = 0x98badcfe;
// H3, from spec
private static final int H3 = 0x10325476;
// H4, from spec
private static final int H4 = 0xc3d2e1f0;
private static final int HConstantsSize = 5;
private static final int HashSizeInBytes = 20;
// 16 words
private static final int BlockSizeInBytes = 16 * 4;
// 80 words
private static final int WArraySize = 80;
// 5-word Array. Starts with well-known constants, ends with SHA
private int[] HConstants;
// 80-word Array.
private int[] WArray;
// 16-word Array. Input bit stream M is divided in chunks of MArray
private byte[] MArray;
// Number of bytes of input already processed towards SHA result
private long bytesProcessed;
// Number of bytes in WArray not processed yet
private int bytesToProcess;
// Optimization, for write
private byte[] oneByte = new byte[1];
/**
* Constructs a new SHAOutputStream.
*/
public SHAOutputStream() {
super();
initialize();
reset();
}
/**
* Constructs a new MD5OutputStream with the given initial state.
*
* @param state The initial state of the output stream. This is what will be
* returned by getHash() if write() is never called.
*
* @throws IllegalArgumentException if state.length is less than 16.
*/
public SHAOutputStream(byte[] state) {
this();
if (state.length < HashSizeInBytes) {
throw new IllegalArgumentException();
}
for (int i = 0; i < 4; i++) {
HConstants[i] = 0;
for (int j = 0; j < 4; j++) {
HConstants[i] += (state[4 * i + j] & 0xFF) << 8 * (3 - j);
}
}
}
/**
* Answers a new instance of the same class as the receiver, whose slots
* have been filled in with the values in the slots of the receiver.
* <p>
* Classes which wish to support cloning must specify that they implement
* the Cloneable interface, since the native implementation checks for this.
*
* @return a complete copy of this object
* @throws CloneNotSupportedException if the component does not implement
* the interface Cloneable
*/
@Override
public Object clone() throws CloneNotSupportedException {
// Calling super takes care of primitive type slots
SHAOutputStream result = (SHAOutputStream) super.clone();
result.HConstants = this.HConstants.clone();
result.WArray = this.WArray.clone();
result.MArray = this.MArray.clone();
result.oneByte = this.oneByte.clone();
return result;
}
/**
* Copies a byte array into the receiver's internal buffer, making the
* adjustments as necessary and keeping the receiver in a consistent state.
*
* @param buffer byte array representation of the bytes
* @param off offset into the source buffer where to start the copying
* @param len how many bytes in the source byte array to copy
*
*/
private void copyToInternalBuffer(byte[] buffer, int off, int len) {
int index;
index = off;
for (int i = bytesToProcess; i < bytesToProcess + len; i++) {
MArray[i] = buffer[index];
index++;
}
bytesToProcess = bytesToProcess + len;
}
/**
* Returns an int array (length = 5) with the SHA value of the bytes written
* to the receiver.
*
* @return The 5 ints that form the SHA value of the bytes written to
* the receiver
*/
public int[] getHash() {
this.padBuffer();
this.processBuffer();
int[] result = HConstants.clone();
// After the user asks for the hash value, the stream is put back to the
// initial state
reset();
return result;
}
/**
* Returns a byte array (length = 20) with the SHA value of the bytes
* written to the receiver.
*
* @return The bytes that form the SHA value of the bytes written to
* the receiver
*/
public byte[] getHashAsBytes() {
byte[] hash = new byte[HashSizeInBytes];
this.padBuffer();
this.processBuffer();
// We need to return HConstants (modified by the loop) as an array of
// bytes. A memcopy would be the fastest.
for (int i = 0; i < (HashSizeInBytes / 4); ++i) {
hash[i * 4] = (byte) (HConstants[i] >>> 24 & 0xff);
hash[i * 4 + 1] = (byte) (HConstants[i] >>> 16 & 0xff);
hash[i * 4 + 2] = (byte) (HConstants[i] >>> 8 & 0xff);
hash[i * 4 + 3] = (byte) (HConstants[i] & 0xff);
}
// After the user asks for the hash value, the stream is put back to the
// initial state
reset();
return hash;
}
/**
* Returns a byte array (length = 20) with the SHA value of the bytes
* written to the receiver.
*
* @return The bytes that form the SHA value of the bytes written to
* the receiver
*/
public byte[] getHashAsBytes(boolean pad) {
byte[] hash = new byte[HashSizeInBytes];
if (pad) {
this.padBuffer();
this.processBuffer();
}
// Convert HConstants to an array of bytes
for (int i = 0; i < (HashSizeInBytes / 4); i++) {
hash[i * 4] = (byte) (HConstants[i] >>> 24 & 0xff);
hash[i * 4 + 1] = (byte) (HConstants[i] >>> 16 & 0xff);
hash[i * 4 + 2] = (byte) (HConstants[i] >>> 8 & 0xff);
hash[i * 4 + 3] = (byte) (HConstants[i] & 0xff);
}
// After the user asks for the hash value, the stream is put back to the
// initial state
reset();
return hash;
}
/**
* Initializes the receiver.
*/
private void initialize() {
HConstants = new int[HConstantsSize];
MArray = new byte[BlockSizeInBytes];
WArray = new int[WArraySize];
}
/**
* Adds extra bytes to the bit stream as required (see the SHA
* specification).
*/
private void padBuffer() {
long lengthInBits;
MArray[bytesToProcess] = (byte) 0x80;
for (int i = bytesToProcess + 1; i < BlockSizeInBytes; i++) {
MArray[i] = (byte) 0;
}
// Get length now because there might be extra padding (length in bits)
lengthInBits = (bytesToProcess + bytesProcessed) * 8;
// 9 extra bytes are needed: 1 for 1000... and 8 for length (long)
if ((bytesToProcess + 9) > BlockSizeInBytes) {
// Not enough space to append length. We need another block for
// padding
// Padding in this buffer only includes 1000000....
this.processBuffer();
// Now put 0's in all the buffer. memfill would be faster
for (int i = 0; i < BlockSizeInBytes; i++) {
MArray[i] = (byte) 0;
}
}
for (int i = 1; i < 9; i++) {
MArray[BlockSizeInBytes - i] = (byte) (lengthInBits & 0xff);
lengthInBits = lengthInBits >>> 8;
}
}
/**
* Core SHA code. Processes the receiver's buffer of bits, computing the
* values towards the final SHA
*/
private void processBuffer() {
int A; // A variable, from spec
int B; // B variable, from spec
int C; // C variable, from spec
int D; // D variable, from spec
int E; // E variable, from spec
int temp; // TEMP, from spec
int t; // t, for iteration, from spec
for (t = 0; t <= 15; t++) { // step a, page 7 of spec. Here we convert 4
// bytes into 1 word, 16 times
WArray[t] = (MArray[4 * t] & 0xff) << 24
| ((MArray[4 * t + 1] & 0xff) << 16)
| ((MArray[4 * t + 2] & 0xff) << 8)
| (MArray[4 * t + 3] & 0xff);
}
for (t = 16; t <= 79; t++) { // step b, page 7 of spec
temp = (WArray[t - 3] ^ WArray[t - 8] ^ WArray[t - 14] ^ WArray[t - 16]);
temp = (temp << 1) | (temp >>> (32 - 1)); // element , Circular
// Shift Left by 1
WArray[t] = temp;
}
// step c, page 7 of spec
A = HConstants[0];
B = HConstants[1];
C = HConstants[2];
D = HConstants[3];
E = HConstants[4];
// step d, page 8 of spec
for (t = 0; t <= 19; t++) {
temp = (A << 5) | (A >>> (32 - 5)); // A , Circular Shift Left by 5
temp = temp + E + WArray[t] + K0_19;
temp = temp + ((B & C) | (~B & D));
E = D;
D = C;
C = (B << 30) | (B >>> (32 - 30)); // B , Circular Shift Left by 30
B = A;
A = temp;
}
for (t = 20; t <= 39; t++) {
temp = (A << 5) | (A >>> (32 - 5)); // A , Circular Shift Left by 5
temp = temp + E + WArray[t] + K20_39;
temp = temp + (B ^ C ^ D);
E = D;
D = C;
C = (B << 30) | (B >>> (32 - 30)); // B , Circular Shift Left by 30
B = A;
A = temp;
}
for (t = 40; t <= 59; t++) {
temp = (A << 5) | (A >>> (32 - 5)); // A , Circular Shift Left by 5
temp = temp + E + WArray[t] + K40_59;
temp = temp + ((B & C) | (B & D) | (C & D));
E = D;
D = C;
C = (B << 30) | (B >>> (32 - 30)); // B , Circular Shift Left by 30
B = A;
A = temp;
}
for (t = 60; t <= 79; t++) {
temp = (A << 5) | (A >>> (32 - 5)); // A , Circular Shift Left by 5
temp = temp + E + WArray[t] + K60_79;
temp = temp + (B ^ C ^ D);
E = D;
D = C;
C = (B << 30) | (B >>> (32 - 30)); // B , Circular Shift Left by 30
B = A;
A = temp;
}
// step e, page 8 of spec
HConstants[0] = HConstants[0] + A;
HConstants[1] = HConstants[1] + B;
HConstants[2] = HConstants[2] + C;
HConstants[3] = HConstants[3] + D;
HConstants[4] = HConstants[4] + E;
// Update number of bytes actually processed
bytesProcessed = bytesProcessed + BlockSizeInBytes;
bytesToProcess = 0; // No pending bytes in the block
}
/**
* Reset this SHAOutputStream to the state it was before any byte was
* written to it.
*/
public void reset() {
HConstants[0] = H0;
HConstants[1] = H1;
HConstants[2] = H2;
HConstants[3] = H3;
HConstants[4] = H4;
bytesProcessed = 0;
bytesToProcess = 0;
}
@Override
public String toString() {
return this.getClass().getName() + ':' + toStringBlock(getHashAsBytes());
}
/**
* Converts a block to a String representation.
*
* @param block
* byte array representation of the bytes
*/
private static String toStringBlock(byte[] block) {
return toStringBlock(block, 0, block.length);
}
/**
* Converts a block to a String representation.
*
* @param block
* byte array representation of the bytes
* @param off
* offset into the block where to start the conversion
* @param len
* how many bytes in the byte array to convert to a printable
* representation
*/
private static String toStringBlock(byte[] block, int off, int len) {
String hexdigits = "0123456789ABCDEF";
StringBuilder buf = new StringBuilder();
buf.append('[');
for (int i = off; i < off + len; ++i) {
buf.append(hexdigits.charAt((block[i] >>> 4) & 0xf));
buf.append(hexdigits.charAt(block[i] & 0xf));
}
buf.append(']');
return buf.toString();
}
/**
* Writes <code>len</code> <code>bytes</code> from this byte array
* <code>buffer</code> starting at offset <code>off</code> to the
* SHAOutputStream. The internal buffer used to compute SHA is updated, and
* the incremental computation of SHA is also performed.
*
* @param buffer
* the buffer to be written
* @param off
* offset in buffer to get bytes
* @param len
* number of bytes in buffer to write
*/
@Override
public void write(byte[] buffer, int off, int len) {
int spaceLeft;
int start;
int bytesLeft;
spaceLeft = BlockSizeInBytes - bytesToProcess;
if (len < spaceLeft) { // Extra bytes are not enough to fill buffer
this.copyToInternalBuffer(buffer, off, len);
return;
}
// Extra bytes are bigger than space in buffer. Process buffer multiple
// times
this.copyToInternalBuffer(buffer, off, spaceLeft);
bytesLeft = len - spaceLeft;
this.processBuffer();
start = off + spaceLeft;
while (bytesLeft >= BlockSizeInBytes) {
this.copyToInternalBuffer(buffer, start, BlockSizeInBytes);
bytesLeft = bytesLeft - BlockSizeInBytes;
this.processBuffer();
start = start + BlockSizeInBytes;
}
if (bytesLeft > 0) {
// Extra bytes at the end, not enough to fill buffer
this.copyToInternalBuffer(buffer, start, bytesLeft);
}
}
/**
* Writes the specified byte <code>b</code> to this OutputStream. Only the
* low order byte of <code>b</code> is written.
*
* @param b
* the byte to be written
*/
@Override
public void write(int b) {
// Not thread-safe because we use a shared one-byte buffer
oneByte[0] = (byte) b;
write(oneByte, 0, 1);
}
}