Skip to content

Hash Generator — Swift source

Compute SHA-1, SHA-256, SHA-384 and SHA-512 hashes of any text at once, using the browser's native SubtleCrypto. Copy each digest. 100% client-side.

This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// hash — SHA-1 / SHA-256 / SHA-384 / SHA-512 digest of a UTF-8 string.
//
// Language: Swift 5.9 (Apple CryptoKit / swift-crypto)
// Source:   CosmoDev polyglot showcase port of the `hash` tool, ported from
//           src/tools/HashGenerator.tsx (the canonical TypeScript island).
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// The TypeScript reference delegates to `crypto.subtle.digest`, and the Rust
// port uses the RustCrypto `sha1`/`sha2` crates. The Swift standard library
// ships no crypto, so the idiomatic path is Apple's CryptoKit — and on
// non-Apple platforms the open-source swift-crypto package, which exposes
// the same SHA1/SHA256/Sha384/SHA512 types under `import Crypto` (the
// ecosystem equivalent named where Rust names its crates). The canImport
// switch below picks the right module either way.
//
// Behavior mirrors the TypeScript reference: UTF-8 input, lowercase hex
// output, and rejection of unknown algorithm names (HashError, the Swift
// standing of the Rust port's HashError enum). SHA-1 is offered for legacy
// compatibility only; it is not collision-resistant.

import Foundation

#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto // swift-crypto on Linux
#endif

/// Canonical algorithm names. The spellings match the TypeScript union so the
/// same string works across every port. The case order matches the order the
/// React island renders.
public enum HashAlgorithm: String, CaseIterable, Sendable {
    case sha1 = "SHA-1"
    case sha256 = "SHA-256"
    case sha384 = "SHA-384"
    case sha512 = "SHA-512"

    /// The algorithm selected when a caller passes an empty name — the
    /// optional-parameter default the TypeScript reference declares.
    public static let `default`: HashAlgorithm = .sha256
}

/// Errors thrown by `digest`. A concrete enum (not a string) so callers can
/// switch on the kind — the same shape as the Rust port's HashError.
public enum HashError: Error, Equatable, Sendable {
    /// The algorithm string was not one of the supported names.
    case unknownAlgorithm(String)
}

/// Lowercase hexadecimal encoding of a byte sequence — Foundation-free, the
/// Swift shape of the Rust port's `to_hex`.
private func toHex<S: Sequence>(_ bytes: S) -> String where S.Element == UInt8 {
    let digits = Array("0123456789abcdef".unicodeScalars)
    var out = String.UnicodeScalarView()
    for byte in bytes {
        out.append(digits[Int(byte >> 4)])
        out.append(digits[Int(byte & 0x0f)])
    }
    return String(out)
}

/// Computes the named digest of `text` and returns it as lowercase hex.
///
/// `text` is UTF-8 encoded before hashing (String.utf8), so the result is
/// correct for arbitrary Unicode (emoji, accents, CJK). Passing an empty
/// `algorithm` selects SHA-256; passing an unknown name throws
/// `HashError.unknownAlgorithm`.
public func digest(_ algorithm: String, _ text: String) throws -> String {
    // A String's utf8 view is the exact byte sequence crypto.subtle.digest
    // receives from TextEncoder in the TypeScript reference.
    let utf8 = Data(text.utf8)

    switch algorithm {
    case HashAlgorithm.sha1.rawValue:
        // CryptoKit keeps SHA-1 behind the Insecure namespace (as does
        // swift-crypto) to signal its broken collision resistance.
        return toHex(Insecure.SHA1.hash(data: utf8))
    case HashAlgorithm.sha256.rawValue:
        return toHex(SHA256.hash(data: utf8))
    case HashAlgorithm.sha384.rawValue:
        return toHex(SHA384.hash(data: utf8))
    case HashAlgorithm.sha512.rawValue:
        return toHex(SHA512.hash(data: utf8))
    case "":
        return toHex(SHA256.hash(data: utf8))
    default:
        throw HashError.unknownAlgorithm(algorithm)
    }
}

/// Computes every supported digest of `text`, returning (algorithm, hex)
/// pairs in the fixed order the React island renders — the Swift shape of
/// the Rust port's `hash_all` vector. Each hash is independent.
public func hashAll(_ text: String) -> [(name: String, hex: String)] {
    // Every name is a canonical constant, so the unknown-name throw is
    // statically unreachable; try! documents that rather than hiding it.
    HashAlgorithm.allCases.map { alg in
        (name: alg.rawValue, hex: try! digest(alg.rawValue, text))
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →