Public/Split-SecretSharingSecret.ps1
|
function Split-SecretSharingSecret { <# .SYNOPSIS Splits a secret into SLIP-0039 mnemonic shares under a group/member quorum scheme. .DESCRIPTION Encrypts Secret (optionally with a Passphrase) into an encrypted master secret, splits it across one or more groups via a GroupThreshold-of-Group.Count Shamir scheme, and splits each group's share across that group's members via its own Threshold-of-Count scheme. Returns one object per member share. For the common case - a single group, T-of-N - pass one -Group entry and leave -GroupThreshold at its default of 1: e.g. -Group @{ Threshold = 3; Count = 5 } for a classic 3-of-5 split. Multi-group hierarchical schemes are supported by passing more than one -Group entry and a -GroupThreshold greater than 1. .PARAMETER Secret The secret to split, e.g. from New-SecretSharingSecret. .PARAMETER Passphrase An optional passphrase. An empty/omitted passphrase is a valid, deliberate SLIP-0039 input - see the .NOTES on Join-SecretSharingSecret for why a wrong passphrase cannot be detected. .PARAMETER Group One hashtable per group, each with Threshold and Count keys, e.g. @{ Threshold = 3; Count = 5 }. At least 1, at most 16 entries. .PARAMETER GroupThreshold The number of groups that must each meet their own member threshold to reconstruct the secret. Defaults to 1 (the whole secret needs only one group, the common single-group case). .PARAMETER Extendable Marks the backup as extendable: shares can be added to the set later without invalidating existing ones, at the cost of the encryption salt not being bound to this specific share set. .PARAMETER IterationExponent Controls the PBKDF2 iteration count for the master-secret cipher (10000 << IterationExponent, divided across 4 rounds). 0-15, default 0. .EXAMPLE PS> $secret = New-SecretSharingSecret -Entropy 128 PS> Split-SecretSharingSecret -Secret $secret -Group @{ Threshold = 3; Count = 5 } Splits a 128-bit secret into 5 shares, any 3 of which reconstruct it. .OUTPUTS PSCustomObject, one per member share, with Identifier, Extendable, IterationExponent, GroupIndex, GroupThreshold, GroupCount, MemberIndex, MemberThreshold, and Mnemonic properties. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [System.Security.SecureString]$Secret, [System.Security.SecureString]$Passphrase, [Parameter(Mandatory)] [ValidateCount(1, 16)] [Hashtable[]]$Group, [ValidateRange(1, 16)] [int]$GroupThreshold = 1, [switch]$Extendable, [ValidateRange(0, 15)] [int]$IterationExponent = 0 ) if ($GroupThreshold -gt $Group.Count) { throw "GroupThreshold ($GroupThreshold) cannot exceed the number of -Group entries supplied ($($Group.Count))." } $groupSpec = @(foreach ($g in $Group) { if (-not ($g.ContainsKey('Threshold') -and $g.ContainsKey('Count'))) { throw "Each -Group entry must be a hashtable with 'Threshold' and 'Count' keys." } $threshold = [int]$g['Threshold'] $count = [int]$g['Count'] if ($threshold -lt 1 -or $threshold -gt 16) { throw "Group Threshold must be between 1 and 16, got $threshold." } if ($count -lt 1 -or $count -gt 16) { throw "Group Count must be between 1 and 16, got $count." } if ($threshold -gt $count) { throw "Group Threshold ($threshold) cannot exceed Group Count ($count)." } [PSCustomObject]@{ Threshold = $threshold; Count = $count } }) $secretByte = ConvertFrom-SecretSharingSecureString -SecureString $Secret $passphraseByte = [byte[]]@() if ($Passphrase) { $passphraseByte = ConvertFrom-SecretSharingPassphraseSecureString -SecureString $Passphrase } $digestConstant = Get-SecretSharingDigestConstant $identifier = Get-SecretSharingRandomIdentifier $encryptedMasterSecret = Protect-SecretSharingMasterSecret -MasterSecret $secretByte -Passphrase $passphraseByte ` -Identifier $identifier -IterationExponent $IterationExponent -Extendable:$Extendable $groupXCoordinate = [byte[]](0..($groupSpec.Count - 1)) if ($GroupThreshold -eq 1) { $groupPoint = Split-SecretSharingShamirSecret -Secret $encryptedMasterSecret -SecretXCoordinate $digestConstant.SecretIndex ` -Threshold 1 -XCoordinate $groupXCoordinate } else { $groupDigest = New-SecretSharingDigestShare -Secret $encryptedMasterSecret $groupFixedPoint = @([PSCustomObject]@{ X = $digestConstant.DigestIndex; Value = $groupDigest }) $groupPoint = Split-SecretSharingShamirSecret -Secret $encryptedMasterSecret -SecretXCoordinate $digestConstant.SecretIndex ` -Threshold $GroupThreshold -XCoordinate $groupXCoordinate -FixedPoint $groupFixedPoint } for ($groupIndex = 0; $groupIndex -lt $groupSpec.Count; $groupIndex++) { $spec = $groupSpec[$groupIndex] $groupShareValue = ($groupPoint | Where-Object { $_.X -eq $groupIndex }).Value $memberXCoordinate = [byte[]](0..($spec.Count - 1)) if ($spec.Threshold -eq 1) { $memberPoint = Split-SecretSharingShamirSecret -Secret $groupShareValue -SecretXCoordinate $digestConstant.SecretIndex ` -Threshold 1 -XCoordinate $memberXCoordinate } else { $memberDigest = New-SecretSharingDigestShare -Secret $groupShareValue $memberFixedPoint = @([PSCustomObject]@{ X = $digestConstant.DigestIndex; Value = $memberDigest }) $memberPoint = Split-SecretSharingShamirSecret -Secret $groupShareValue -SecretXCoordinate $digestConstant.SecretIndex ` -Threshold $spec.Threshold -XCoordinate $memberXCoordinate -FixedPoint $memberFixedPoint } foreach ($member in $memberPoint) { $dataWord = ConvertTo-SecretSharingShareWord -Identifier $identifier -Extendable:$Extendable ` -IterationExponent $IterationExponent -GroupIndex $groupIndex -GroupThreshold $GroupThreshold ` -GroupCount $groupSpec.Count -MemberIndex ([int]$member.X) -MemberThreshold $spec.Threshold -Value $member.Value $checksum = New-SecretSharingChecksum -Data $dataWord -Extendable:$Extendable $fullWord = [int[]]($dataWord + $checksum) $mnemonic = ($fullWord | ForEach-Object { ConvertTo-SecretSharingMnemonicWord -Index $_ }) -join ' ' [PSCustomObject]@{ Identifier = $identifier Extendable = [bool]$Extendable IterationExponent = $IterationExponent GroupIndex = $groupIndex GroupThreshold = $GroupThreshold GroupCount = $groupSpec.Count MemberIndex = [int]$member.X MemberThreshold = $spec.Threshold Mnemonic = $mnemonic } } } } |