# Digital evidence, hashes and timelines

Separate byte integrity from source truth. Preserve original timestamps, produce a UTC view and trace evidence handling and purpose.

Original AI-assisted SecNet educational content. Fictional local examples; not a standards provider publication, endorsement or compliance evidence. Page exercises do not create account progress.

Sources checked: 2026-10-21

## What does a hash establish?

A matching SHA256 helps compare byte integrity; it cannot by itself establish truthful contents, authorized acquisition or legal admissibility. Record acquisition method, origin, time, tool version and handler separately. Preserve originals and work from copies; do not present transformed CSV/report bytes under the original hash. Actual evidence requires the organization’s authorized procedures and applicable requirements.

## Normalize clocks, retain uncertainty

Preserve the original timestamp/offset and store UTC as a separate view. Do not assume UTC for missing offsets. This narrow teaching profile rejects -00:00; Z expresses UTC, not proof of clock accuracy or the source’s local offset. This tool accepts a restricted RFC3339 form with seconds and up to milliseconds and rejects leap seconds. Impossible dates and duplicate event IDs produce errors. It does not measure clock skew; the analyst must record that uncertainty separately.

## Custody and transfer chain

Trace who acquired/transferred material, when, for what purpose and with which integrity checks. Uploading to a shared folder does not automatically create custody history. Bind access and retention decisions to purpose and organizational legal/contractual requirements through authorized teams. NISTIR8387 focuses on preservation;800-86 is an older foundational IT-forensics reference, not a current tool manual or legal advice.

## Harbor-17 actual hashes and normalization

verify-fixtures hashes actual bytes of two fixed-name synthetic texts and compares case metadata; labels never become arbitrary local paths. The browser shows hash metadata without verifying file bytes. Raw EML, EVTX and PCAP are not parsed. The timeline CLI reads only the explicitly named normalized JSON and additionally hashes that input. Sorting and a two-minute correlation are not breach decisions; missing session, clock and user evidence remains visible.

## Working sequence

1. Record origin and acquisition authority.
2. Separate original bytes and working copies.
3. Bind hashes to method and handler.
4. Retain raw time/offset alongside UTC.
5. Report custody and uncertainty.

## 1. Hash matched

Hash matching supports byte integrity, not source truth or admissibility. Origin, acquisition and custody records are separate requirements.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.hashMatches
```

### Corrected implementation
```js
s=>s.hashMatches&&s.originRecorded&&s.acquisitionRecorded&&s.custodyRecorded
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "supported-case",
      "input": {
        "hashMatches": true,
        "originRecorded": true,
        "acquisitionRecorded": true,
        "custodyRecorded": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "matching-unknown-file",
      "input": {
        "hashMatches": true,
        "originRecorded": false,
        "acquisitionRecorded": false,
        "custodyRecorded": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "missing-transfer",
      "input": {
        "hashMatches": true,
        "originRecorded": true,
        "acquisitionRecorded": true,
        "custodyRecorded": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "supported-case",
      "input": {
        "hashMatches": true,
        "originRecorded": true,
        "acquisitionRecorded": true,
        "custodyRecorded": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "matching-unknown-file",
      "input": {
        "hashMatches": true,
        "originRecorded": false,
        "acquisitionRecorded": false,
        "custodyRecorded": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "missing-transfer",
      "input": {
        "hashMatches": true,
        "originRecorded": true,
        "acquisitionRecorded": true,
        "custodyRecorded": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 2. Sorted: is the order certain?

Local times cannot be reliably ordered without offsets. Preserve raw time and clock uncertainty; input ordering of ties is not causation.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.sorted
```

### Corrected implementation
```js
s=>s.sorted&&s.offsetExplicit&&s.rawTimePreserved&&s.clockUncertaintyRecorded
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "supported-case",
      "input": {
        "sorted": true,
        "offsetExplicit": true,
        "rawTimePreserved": true,
        "clockUncertaintyRecorded": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "local-time-only",
      "input": {
        "sorted": true,
        "offsetExplicit": false,
        "rawTimePreserved": true,
        "clockUncertaintyRecorded": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "assumed-perfect-clock",
      "input": {
        "sorted": true,
        "offsetExplicit": true,
        "rawTimePreserved": true,
        "clockUncertaintyRecorded": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "supported-case",
      "input": {
        "sorted": true,
        "offsetExplicit": true,
        "rawTimePreserved": true,
        "clockUncertaintyRecorded": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "local-time-only",
      "input": {
        "sorted": true,
        "offsetExplicit": false,
        "rawTimePreserved": true,
        "clockUncertaintyRecorded": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "assumed-perfect-clock",
      "input": {
        "sorted": true,
        "offsetExplicit": true,
        "rawTimePreserved": true,
        "clockUncertaintyRecorded": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 3. The shared-folder file

Uploading does not establish handler, time and purpose history. Transfer records and scoped access are needed to support custody claims.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.uploaded
```

### Corrected implementation
```js
s=>s.uploaded&&s.handlerRecorded&&s.transferTimeRecorded&&s.purposeRecorded&&s.accessScoped
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "supported-case",
      "input": {
        "uploaded": true,
        "handlerRecorded": true,
        "transferTimeRecorded": true,
        "purposeRecorded": true,
        "accessScoped": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "shared-folder-only",
      "input": {
        "uploaded": true,
        "handlerRecorded": false,
        "transferTimeRecorded": false,
        "purposeRecorded": false,
        "accessScoped": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "unknown-purpose",
      "input": {
        "uploaded": true,
        "handlerRecorded": true,
        "transferTimeRecorded": true,
        "purposeRecorded": false,
        "accessScoped": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "supported-case",
      "input": {
        "uploaded": true,
        "handlerRecorded": true,
        "transferTimeRecorded": true,
        "purposeRecorded": true,
        "accessScoped": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "shared-folder-only",
      "input": {
        "uploaded": true,
        "handlerRecorded": false,
        "transferTimeRecorded": false,
        "purposeRecorded": false,
        "accessScoped": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "unknown-purpose",
      "input": {
        "uploaded": true,
        "handlerRecorded": true,
        "transferTimeRecorded": true,
        "purposeRecorded": false,
        "accessScoped": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## Exit evidence

- Origin/acquisition/hash/transfer register
- Timeline with original offsets and UTC

## References

- [NIST IR 8387 · Digital evidence preservation (2022)](https://www.nist.gov/publications/digital-evidence-preservation-considerations-evidence-handlers)
- [NIST SP 800-86 · IT forensics foundation (2006)](https://csrc.nist.gov/pubs/sp/800/86/final)
- [RFC 3339 · Timestamp/offset foundation (updated by RFC9557)](https://www.rfc-editor.org/rfc/rfc3339)
