Merge changes from topic 'SHAttered'

* changes:
  Switch to pure Java SHA1 for ObjectId
  Pure Java SHA-1
This commit is contained in:
Jonathan Nieder 2017-02-26 18:17:02 -05:00 committed by Gerrit Code Review @ Eclipse.org
commit c90ab1a774
11 changed files with 480 additions and 38 deletions

View File

@ -49,6 +49,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
org.eclipse.jgit.treewalk.filter;version="[4.7.0,4.8.0)", org.eclipse.jgit.treewalk.filter;version="[4.7.0,4.8.0)",
org.eclipse.jgit.util;version="[4.7.0,4.8.0)", org.eclipse.jgit.util;version="[4.7.0,4.8.0)",
org.eclipse.jgit.util.io;version="[4.7.0,4.8.0)", org.eclipse.jgit.util.io;version="[4.7.0,4.8.0)",
org.eclipse.jgit.util.sha1;version="[4.7.0,4.8.0)",
org.junit;version="[4.4.0,5.0.0)", org.junit;version="[4.4.0,5.0.0)",
org.junit.experimental.theories;version="[4.4.0,5.0.0)", org.junit.experimental.theories;version="[4.4.0,5.0.0)",
org.junit.rules;version="[4.11.0,5.0.0)", org.junit.rules;version="[4.11.0,5.0.0)",

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.util.sha1;
import static org.junit.Assert.assertEquals;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public class SHA1Test {
private static final String TEST1 = "abc";
private static final String TEST2a = "abcdbcdecdefdefgefghfghighijhi";
private static final String TEST2b = "jkijkljklmklmnlmnomnopnopq";
private static final String TEST2 = TEST2a + TEST2b;
@Test
public void test0() throws NoSuchAlgorithmException {
ObjectId exp = ObjectId
.fromString("da39a3ee5e6b4b0d3255bfef95601890afd80709");
MessageDigest m = MessageDigest.getInstance("SHA-1");
m.update(new byte[] {});
ObjectId m1 = ObjectId.fromRaw(m.digest());
SHA1 s = SHA1.newInstance();
s.update(new byte[] {});
ObjectId s1 = ObjectId.fromRaw(s.digest());
assertEquals(m1, s1);
assertEquals(exp, s1);
}
@Test
public void test1() throws NoSuchAlgorithmException {
ObjectId exp = ObjectId
.fromString("a9993e364706816aba3e25717850c26c9cd0d89d");
MessageDigest m = MessageDigest.getInstance("SHA-1");
m.update(TEST1.getBytes(StandardCharsets.UTF_8));
ObjectId m1 = ObjectId.fromRaw(m.digest());
SHA1 s = SHA1.newInstance();
s.update(TEST1.getBytes(StandardCharsets.UTF_8));
ObjectId s1 = ObjectId.fromRaw(s.digest());
assertEquals(m1, s1);
assertEquals(exp, s1);
}
@Test
public void test2() throws NoSuchAlgorithmException {
ObjectId exp = ObjectId
.fromString("84983e441c3bd26ebaae4aa1f95129e5e54670f1");
MessageDigest m = MessageDigest.getInstance("SHA-1");
m.update(TEST2.getBytes(StandardCharsets.UTF_8));
ObjectId m1 = ObjectId.fromRaw(m.digest());
SHA1 s = SHA1.newInstance();
s.update(TEST2.getBytes(StandardCharsets.UTF_8));
ObjectId s1 = ObjectId.fromRaw(s.digest());
assertEquals(m1, s1);
assertEquals(exp, s1);
}
}

View File

@ -138,6 +138,7 @@ Export-Package: org.eclipse.jgit.annotations;version="4.7.0",
org.eclipse.jgit.storage.file, org.eclipse.jgit.storage.file,
org.ietf.jgss", org.ietf.jgss",
org.eclipse.jgit.util.io;version="4.7.0", org.eclipse.jgit.util.io;version="4.7.0",
org.eclipse.jgit.util.sha1;version="4.7.0",
org.eclipse.jgit.util.time;version="4.7.0" org.eclipse.jgit.util.time;version="4.7.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",

View File

@ -90,6 +90,7 @@
import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.NB;
import org.eclipse.jgit.util.TemporaryBuffer; import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.io.CountingOutputStream; import org.eclipse.jgit.util.io.CountingOutputStream;
import org.eclipse.jgit.util.sha1.SHA1;
/** Inserts objects into the DFS. */ /** Inserts objects into the DFS. */
public class DfsInserter extends ObjectInserter { public class DfsInserter extends ObjectInserter {
@ -168,7 +169,7 @@ public ObjectId insert(int type, long len, InputStream in)
} }
long offset = beginObject(type, len); long offset = beginObject(type, len);
MessageDigest md = digest(); SHA1 md = SHA1.newInstance();
md.update(Constants.encodedTypeString(type)); md.update(Constants.encodedTypeString(type));
md.update((byte) ' '); md.update((byte) ' ');
md.update(Constants.encodeASCII(len)); md.update(Constants.encodeASCII(len));
@ -183,7 +184,7 @@ public ObjectId insert(int type, long len, InputStream in)
len -= n; len -= n;
} }
packOut.compress.finish(); packOut.compress.finish();
return endObject(ObjectId.fromRaw(md.digest()), offset); return endObject(md.toObjectId(), offset);
} }
private byte[] insertBuffer(long len) { private byte[] insertBuffer(long len) {

View File

@ -49,12 +49,11 @@
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream; import java.util.zip.DeflaterOutputStream;
@ -69,6 +68,7 @@
import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.transport.PackParser;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.sha1.SHA1;
/** Creates loose objects in a {@link ObjectDirectory}. */ /** Creates loose objects in a {@link ObjectDirectory}. */
class ObjectDirectoryInserter extends ObjectInserter { class ObjectDirectoryInserter extends ObjectInserter {
@ -140,9 +140,9 @@ ObjectId insert(int type, long len, InputStream is, boolean createDuplicate)
return insert(type, buf, 0, actLen, createDuplicate); return insert(type, buf, 0, actLen, createDuplicate);
} else { } else {
MessageDigest md = digest(); SHA1 md = SHA1.newInstance();
File tmp = toTemp(md, type, len, is); File tmp = toTemp(md, type, len, is);
ObjectId id = ObjectId.fromRaw(md.digest()); ObjectId id = md.toObjectId();
return insertOneObject(tmp, id, createDuplicate); return insertOneObject(tmp, id, createDuplicate);
} }
} }
@ -193,7 +193,7 @@ public void close() {
} }
@SuppressWarnings("resource" /* java 7 */) @SuppressWarnings("resource" /* java 7 */)
private File toTemp(final MessageDigest md, final int type, long len, private File toTemp(final SHA1 md, final int type, long len,
final InputStream is) throws IOException, FileNotFoundException, final InputStream is) throws IOException, FileNotFoundException,
Error { Error {
boolean delete = true; boolean delete = true;
@ -205,7 +205,7 @@ private File toTemp(final MessageDigest md, final int type, long len,
if (config.getFSyncObjectFiles()) if (config.getFSyncObjectFiles())
out = Channels.newOutputStream(fOut.getChannel()); out = Channels.newOutputStream(fOut.getChannel());
DeflaterOutputStream cOut = compress(out); DeflaterOutputStream cOut = compress(out);
DigestOutputStream dOut = new DigestOutputStream(cOut, md); SHA1OutputStream dOut = new SHA1OutputStream(cOut, md);
writeHeader(dOut, type, len); writeHeader(dOut, type, len);
final byte[] buf = buffer(); final byte[] buf = buffer();
@ -285,4 +285,25 @@ private static EOFException shortInput(long missing) {
return new EOFException(MessageFormat.format( return new EOFException(MessageFormat.format(
JGitText.get().inputDidntMatchLength, Long.valueOf(missing))); JGitText.get().inputDidntMatchLength, Long.valueOf(missing)));
} }
private static class SHA1OutputStream extends FilterOutputStream {
private final SHA1 md;
SHA1OutputStream(OutputStream out, SHA1 md) {
super(out);
this.md = md;
}
@Override
public void write(int b) throws IOException {
md.update((byte) b);
out.write(b);
}
@Override
public void write(byte[] in, int p, int n) throws IOException {
md.update(in, p, n);
out.write(in, p, n);
}
}
} }

View File

@ -208,6 +208,24 @@ public void fromRaw(final int[] ints, final int p) {
w5 = ints[p + 4]; w5 = ints[p + 4];
} }
/**
* Convert an ObjectId from binary representation expressed in integers.
*
* @param a
* @param b
* @param c
* @param d
* @param e
* @since 4.7
*/
public void set(int a, int b, int c, int d, int e) {
w1 = a;
w2 = b;
w3 = c;
w4 = d;
w5 = e;
}
/** /**
* Convert an ObjectId from hex characters (US-ASCII). * Convert an ObjectId from hex characters (US-ASCII).
* *

View File

@ -248,8 +248,17 @@ private static final ObjectId fromHexString(final byte[] bs, int p) {
} }
} }
ObjectId(final int new_1, final int new_2, final int new_3, /**
final int new_4, final int new_5) { * Construct an ObjectId from 160 bits provided in 5 words.
*
* @param new_1
* @param new_2
* @param new_3
* @param new_4
* @param new_5
* @since 4.7
*/
public ObjectId(int new_1, int new_2, int new_3, int new_4, int new_5) {
w1 = new_1; w1 = new_1;
w2 = new_2; w2 = new_2;
w3 = new_3; w3 = new_3;

View File

@ -50,10 +50,10 @@
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.MessageDigest;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.transport.PackParser;
import org.eclipse.jgit.util.sha1.SHA1;
/** /**
* Inserts objects into an existing {@code ObjectDatabase}. * Inserts objects into an existing {@code ObjectDatabase}.
@ -177,15 +177,11 @@ public void close() {
} }
} }
/** Digest to compute the name of an object. */
private final MessageDigest digest;
/** Temporary working buffer for streaming data through. */ /** Temporary working buffer for streaming data through. */
private byte[] tempBuffer; private byte[] tempBuffer;
/** Create a new inserter for a database. */ /** Create a new inserter for a database. */
protected ObjectInserter() { protected ObjectInserter() {
digest = Constants.newMessageDigest();
} }
/** /**
@ -220,9 +216,8 @@ protected byte[] buffer() {
} }
/** @return digest to help compute an ObjectId */ /** @return digest to help compute an ObjectId */
protected MessageDigest digest() { protected SHA1 digest() {
digest.reset(); return SHA1.newInstance();
return digest;
} }
/** /**
@ -252,13 +247,13 @@ public ObjectId idFor(int type, byte[] data) {
* @return the name of the object. * @return the name of the object.
*/ */
public ObjectId idFor(int type, byte[] data, int off, int len) { public ObjectId idFor(int type, byte[] data, int off, int len) {
MessageDigest md = digest(); SHA1 md = SHA1.newInstance();
md.update(Constants.encodedTypeString(type)); md.update(Constants.encodedTypeString(type));
md.update((byte) ' '); md.update((byte) ' ');
md.update(Constants.encodeASCII(len)); md.update(Constants.encodeASCII(len));
md.update((byte) 0); md.update((byte) 0);
md.update(data, off, len); md.update(data, off, len);
return ObjectId.fromRaw(md.digest()); return md.toObjectId();
} }
/** /**
@ -277,7 +272,7 @@ public ObjectId idFor(int type, byte[] data, int off, int len) {
*/ */
public ObjectId idFor(int objectType, long length, InputStream in) public ObjectId idFor(int objectType, long length, InputStream in)
throws IOException { throws IOException {
MessageDigest md = digest(); SHA1 md = SHA1.newInstance();
md.update(Constants.encodedTypeString(objectType)); md.update(Constants.encodedTypeString(objectType));
md.update((byte) ' '); md.update((byte) ' ');
md.update(Constants.encodeASCII(length)); md.update(Constants.encodeASCII(length));
@ -290,7 +285,7 @@ public ObjectId idFor(int objectType, long length, InputStream in)
md.update(buf, 0, n); md.update(buf, 0, n);
length -= n; length -= n;
} }
return ObjectId.fromRaw(md.digest()); return md.toObjectId();
} }
/** /**

View File

@ -83,6 +83,7 @@
import org.eclipse.jgit.util.BlockList; import org.eclipse.jgit.util.BlockList;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.NB;
import org.eclipse.jgit.util.sha1.SHA1;
/** /**
* Parses a pack stream and imports it for an {@link ObjectInserter}. * Parses a pack stream and imports it for an {@link ObjectInserter}.
@ -116,8 +117,6 @@ public static enum Source {
private byte[] hdrBuf; private byte[] hdrBuf;
private final MessageDigest objectDigest;
private final MutableObjectId tempObjectId; private final MutableObjectId tempObjectId;
private InputStream in; private InputStream in;
@ -206,7 +205,6 @@ protected PackParser(final ObjectDatabase odb, final InputStream src) {
buf = new byte[BUFFER_SIZE]; buf = new byte[BUFFER_SIZE];
tempBuffer = new byte[BUFFER_SIZE]; tempBuffer = new byte[BUFFER_SIZE];
hdrBuf = new byte[64]; hdrBuf = new byte[64];
objectDigest = Constants.newMessageDigest();
tempObjectId = new MutableObjectId(); tempObjectId = new MutableObjectId();
packDigest = Constants.newMessageDigest(); packDigest = Constants.newMessageDigest();
checkObjectCollisions = true; checkObjectCollisions = true;
@ -667,12 +665,13 @@ private void resolveDeltas(DeltaVisit visit, final int type,
JGitText.get().corruptionDetectedReReadingAt, JGitText.get().corruptionDetectedReReadingAt,
Long.valueOf(visit.delta.position))); Long.valueOf(visit.delta.position)));
SHA1 objectDigest = SHA1.newInstance();
objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update(Constants.encodedTypeString(type));
objectDigest.update((byte) ' '); objectDigest.update((byte) ' ');
objectDigest.update(Constants.encodeASCII(visit.data.length)); objectDigest.update(Constants.encodeASCII(visit.data.length));
objectDigest.update((byte) 0); objectDigest.update((byte) 0);
objectDigest.update(visit.data); objectDigest.update(visit.data);
tempObjectId.fromRaw(objectDigest.digest(), 0); objectDigest.digest(tempObjectId);
verifySafeObject(tempObjectId, type, visit.data); verifySafeObject(tempObjectId, type, visit.data);
@ -1024,6 +1023,7 @@ private void indexOneObject() throws IOException {
private void whole(final long pos, final int type, final long sz) private void whole(final long pos, final int type, final long sz)
throws IOException { throws IOException {
SHA1 objectDigest = SHA1.newInstance();
objectDigest.update(Constants.encodedTypeString(type)); objectDigest.update(Constants.encodedTypeString(type));
objectDigest.update((byte) ' '); objectDigest.update((byte) ' ');
objectDigest.update(Constants.encodeASCII(sz)); objectDigest.update(Constants.encodeASCII(sz));
@ -1043,7 +1043,7 @@ private void whole(final long pos, final int type, final long sz)
cnt += r; cnt += r;
} }
inf.close(); inf.close();
tempObjectId.fromRaw(objectDigest.digest(), 0); objectDigest.digest(tempObjectId);
checkContentLater = isCheckObjectCollisions() checkContentLater = isCheckObjectCollisions()
&& readCurs.has(tempObjectId); && readCurs.has(tempObjectId);
data = null; data = null;
@ -1051,7 +1051,7 @@ private void whole(final long pos, final int type, final long sz)
} else { } else {
data = inflateAndReturn(Source.INPUT, sz); data = inflateAndReturn(Source.INPUT, sz);
objectDigest.update(data); objectDigest.update(data);
tempObjectId.fromRaw(objectDigest.digest(), 0); objectDigest.digest(tempObjectId);
verifySafeObject(tempObjectId, type, data); verifySafeObject(tempObjectId, type, data);
} }

View File

@ -56,7 +56,6 @@
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder; import java.nio.charset.CharsetEncoder;
import java.security.MessageDigest;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -99,6 +98,7 @@
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
import org.eclipse.jgit.util.io.AutoLFInputStream; import org.eclipse.jgit.util.io.AutoLFInputStream;
import org.eclipse.jgit.util.io.EolStreamTypeUtil; import org.eclipse.jgit.util.io.EolStreamTypeUtil;
import org.eclipse.jgit.util.sha1.SHA1;
/** /**
* Walks a working directory tree as part of a {@link TreeWalk}. * Walks a working directory tree as part of a {@link TreeWalk}.
@ -364,7 +364,7 @@ private byte[] idBufferBlob(final Entry e) {
if (is == null) if (is == null)
return zeroid; return zeroid;
try { try {
state.initializeDigestAndReadBuffer(); state.initializeReadBuffer();
final long len = e.getLength(); final long len = e.getLength();
InputStream filteredIs = possiblyFilteredInputStream(e, is, len, InputStream filteredIs = possiblyFilteredInputStream(e, is, len,
@ -1099,10 +1099,9 @@ private static long computeLength(InputStream in) throws IOException {
} }
private byte[] computeHash(InputStream in, long length) throws IOException { private byte[] computeHash(InputStream in, long length) throws IOException {
final MessageDigest contentDigest = state.contentDigest; SHA1 contentDigest = SHA1.newInstance();
final byte[] contentReadBuffer = state.contentReadBuffer; final byte[] contentReadBuffer = state.contentReadBuffer;
contentDigest.reset();
contentDigest.update(hblob); contentDigest.update(hblob);
contentDigest.update((byte) ' '); contentDigest.update((byte) ' ');
@ -1330,9 +1329,6 @@ private static final class IteratorState {
/** File name character encoder. */ /** File name character encoder. */
final CharsetEncoder nameEncoder; final CharsetEncoder nameEncoder;
/** Digest computer for {@link #contentId} computations. */
MessageDigest contentDigest;
/** Buffer used to perform {@link #contentId} computations. */ /** Buffer used to perform {@link #contentId} computations. */
byte[] contentReadBuffer; byte[] contentReadBuffer;
@ -1347,9 +1343,8 @@ private static final class IteratorState {
this.nameEncoder = Constants.CHARSET.newEncoder(); this.nameEncoder = Constants.CHARSET.newEncoder();
} }
void initializeDigestAndReadBuffer() { void initializeReadBuffer() {
if (contentDigest == null) { if (contentReadBuffer == null) {
contentDigest = Constants.newMessageDigest();
contentReadBuffer = new byte[BUFFER_SIZE]; contentReadBuffer = new byte[BUFFER_SIZE];
} }
} }

View File

@ -0,0 +1,289 @@
/*
* Copyright (C) 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.util.sha1;
import java.util.Arrays;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.NB;
/**
* Pure Java implementation of SHA-1 from FIPS 180-1 / RFC 3174.
*
* <p>
* See <a href="https://tools.ietf.org/html/rfc3174">RFC 3174</a>.
*
* @since 4.7
*/
public class SHA1 {
/** @return a new context to compute a SHA-1 hash of data. */
public static SHA1 newInstance() {
return new SHA1();
}
// Magic initialization constants defined by FIPS180.
private int h0 = 0x67452301;
private int h1 = 0xEFCDAB89;
private int h2 = 0x98BADCFE;
private int h3 = 0x10325476;
private int h4 = 0xC3D2E1F0;
private final int[] w = new int[80];
/** Buffer to accumulate partial blocks to 64 byte alignment. */
private final byte[] buffer = new byte[64];
/** Total number of bytes in the message. */
private long length;
private SHA1() {
}
/**
* Update the digest computation by adding a byte.
*
* @param b
*/
public void update(byte b) {
int bufferLen = (int) (length & 63);
length++;
buffer[bufferLen] = b;
if (bufferLen == 63) {
compress(buffer, 0);
}
}
/**
* Update the digest computation by adding bytes to the message.
*
* @param in
* input array of bytes.
*/
public void update(byte[] in) {
update(in, 0, in.length);
}
/**
* Update the digest computation by adding bytes to the message.
*
* @param in
* input array of bytes.
* @param p
* offset to start at from {@code in}.
* @param len
* number of bytes to hash.
*/
public void update(byte[] in, int p, int len) {
// SHA-1 compress can only process whole 64 byte blocks.
// Hold partial updates in buffer, whose length is the low bits.
int bufferLen = (int) (length & 63);
length += len;
if (bufferLen > 0) {
int n = Math.min(64 - bufferLen, len);
System.arraycopy(in, p, buffer, bufferLen, n);
p += n;
len -= n;
if (bufferLen + n < 64) {
return;
}
compress(buffer, 0);
}
while (len >= 64) {
compress(in, p);
p += 64;
len -= 64;
}
if (len > 0) {
System.arraycopy(in, p, buffer, 0, len);
}
}
private void compress(byte[] block, int p) {
// Method 1 from RFC 3174 section 6.1.
// Method 2 (circular queue of 16 words) is slower.
int a = h0, b = h1, c = h2, d = h3, e = h4;
// Round 1: 0 <= t <= 15 comes from the input block.
for (int t = 0; t < 16; t++) {
int temp = NB.decodeInt32(block, p + (t << 2));
w[t] = temp;
temp += ((a << 5) | (a >>> 27)) // S^5(A)
+ (((c ^ d) & b) ^ d) // f: 0 <= t <= 19
+ e + 0x5A827999;
e = d;
d = c;
c = (b << 30) | (b >>> 2); // S^30(B)
b = a;
a = temp;
}
// RFC 3174 6.1.b, extend state vector to 80 words.
for (int t = 16; t < 80; t++) {
int x = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
w[t] = (x << 1) | (x >>> 31); // S^1(...)
}
// Round 1: tail
for (int t = 16; t < 20; t++) {
int temp = ((a << 5) | (a >>> 27)) // S^5(A)
+ (((c ^ d) & b) ^ d) // f: 0 <= t <= 19
+ e + w[t] + 0x5A827999;
e = d;
d = c;
c = (b << 30) | (b >>> 2); // S^30(B)
b = a;
a = temp;
}
// Round 2
for (int t = 20; t < 40; t++) {
int temp = ((a << 5) | (a >>> 27)) // S^5(A)
+ (b ^ c ^ d) // f: 20 <= t <= 39
+ e + w[t] + 0x6ED9EBA1;
e = d;
d = c;
c = (b << 30) | (b >>> 2); // S^30(B)
b = a;
a = temp;
}
// Round 3
for (int t = 40; t < 60; t++) {
int temp = ((a << 5) | (a >>> 27)) // S^5(A)
+ ((b & c) | (d & (b | c))) // f: 40 <= t <= 59
+ e + w[t] + 0x8F1BBCDC;
e = d;
d = c;
c = (b << 30) | (b >>> 2); // S^30(B)
b = a;
a = temp;
}
// Round 4
for (int t = 60; t < 80; t++) {
int temp = ((a << 5) | (a >>> 27)) // S^5(A)
+ (b ^ c ^ d) // f: 60 <= t <= 79
+ e + w[t] + 0xCA62C1D6;
e = d;
d = c;
c = (b << 30) | (b >>> 2); // S^30(B)
b = a;
a = temp;
}
h0 += a;
h1 += b;
h2 += c;
h3 += d;
h4 += e;
}
private void finish() {
int bufferLen = (int) (length & 63);
if (bufferLen > 55) {
// Last block is too small; pad, compress, pad another block.
buffer[bufferLen++] = (byte) 0x80;
Arrays.fill(buffer, bufferLen, 64, (byte) 0);
compress(buffer, 0);
Arrays.fill(buffer, 0, 56, (byte) 0);
} else {
// Last block can hold padding and length.
buffer[bufferLen++] = (byte) 0x80;
Arrays.fill(buffer, bufferLen, 56, (byte) 0);
}
// SHA-1 appends the length of the message in bits after the
// padding block (above). Here length is in bytes. Multiply by
// 8 by shifting by 3 as part of storing the 64 bit byte length
// into the two words expected in the trailer.
NB.encodeInt32(buffer, 56, (int) (length >>> (32 - 3)));
NB.encodeInt32(buffer, 60, (int) (length << 3));
compress(buffer, 0);
}
/**
* Finish the digest and return the resulting hash.
* <p>
* Once {@code digest()} is called, this instance should be discarded.
*
* @return the bytes for the resulting hash.
*/
public byte[] digest() {
finish();
byte[] b = new byte[20];
NB.encodeInt32(b, 0, h0);
NB.encodeInt32(b, 4, h1);
NB.encodeInt32(b, 8, h2);
NB.encodeInt32(b, 12, h3);
NB.encodeInt32(b, 16, h4);
return b;
}
/**
* Finish the digest and return the resulting hash.
* <p>
* Once {@code digest()} is called, this instance should be discarded.
*
* @return the ObjectId for the resulting hash.
*/
public ObjectId toObjectId() {
finish();
return new ObjectId(h0, h1, h2, h3, h4);
}
/**
* Finish the digest and return the resulting hash.
* <p>
* Once {@code digest()} is called, this instance should be discarded.
*
* @param id
* destination to copy the digest to.
*/
public void digest(MutableObjectId id) {
finish();
id.set(h0, h1, h2, h3, h4);
}
}