# Weak Password Hashing via Unsalted SHA-256

Language: Python
Severity: High
CWE: CWE-916

## Source
7, 15

## Flow
7-8, 15-16

## Sink
8, 16

## Vulnerable Code
```python
import hashlib
import boto3

def register_iot_device(device_id, admin_pin):
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    devices_table = dynamodb.Table('IoTDeviceRegistry')
    hashed_pin = hashlib.sha256(admin_pin.encode()).hexdigest()
    devices_table.put_item(Item={'device_id': device_id, 'admin_pin': hashed_pin, 'status': 'active'})
    return {'success': True, 'device_id': device_id, 'pin_hash': hashed_pin}

def authenticate_iot_device(device_id, provided_pin):
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    devices_table = dynamodb.Table('IoTDeviceRegistry')
    response = devices_table.get_item(Key={'device_id': device_id})
    stored_hash = response['Item']['admin_pin']
    return hashlib.sha256(provided_pin.encode()).hexdigest() == stored_hash
```

## Explanation

The code uses unsalted SHA-256 hashing for admin PINs, making it vulnerable to rainbow table and dictionary attacks. Without salt, identical PINs produce identical hashes, allowing attackers to precompute hashes of common PINs and quickly crack the stored credentials through hash lookup tables.

## Remediation

The fix replaces unsalted SHA-256 with bcrypt, which automatically generates a unique random salt per hash and uses a computationally expensive key derivation function (with 12 rounds of work factor). This makes rainbow table attacks infeasible since each PIN has a unique salt, and brute-force attacks are dramatically slowed by bcrypt's intentional computational cost. The fix also removes the pin_hash from the registration return value to avoid leaking sensitive data and adds a check for missing items during authentication.

## Secure Code
```python
import bcrypt
import boto3

def register_iot_device(device_id, admin_pin):
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    devices_table = dynamodb.Table('IoTDeviceRegistry')
    hashed_pin = bcrypt.hashpw(admin_pin.encode(), bcrypt.gensalt(rounds=12)).decode('utf-8')
    devices_table.put_item(Item={'device_id': device_id, 'admin_pin': hashed_pin, 'status': 'active'})
    return {'success': True, 'device_id': device_id}

def authenticate_iot_device(device_id, provided_pin):
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    devices_table = dynamodb.Table('IoTDeviceRegistry')
    response = devices_table.get_item(Key={'device_id': device_id})
    if 'Item' not in response:
        return False
    stored_hash = response['Item']['admin_pin']
    return bcrypt.checkpw(provided_pin.encode(), stored_hash.encode('utf-8'))
```
