# Weak Password Hashing via unsalted MD5 in hashlib.md5()

Language: Python
Severity: High
CWE: CWE-328

## Source
7

## Flow
7-11

## Sink
11

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

def provision_iot_device_credentials(device_mac, admin_pin):
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    device_table = dynamodb.Table('IoTDeviceRegistry')
    hashed_pin = hashlib.md5(admin_pin.encode()).hexdigest()
    device_table.put_item(
        Item={
            'device_id': device_mac,
            'admin_credential': hashed_pin,
            'provisioned_at': str(datetime.now()),
            'status': 'active'
        }
    )
    return {'device_id': device_mac, 'credential_hash': hashed_pin}
```

## Explanation

The code uses MD5 hashing without salt to hash admin PINs before storing them in DynamoDB. MD5 is cryptographically broken and allows attackers to use rainbow tables or precomputed hash databases to reverse the hashes and retrieve original PINs, especially since PINs are typically short numeric values.

## Remediation

The fix replaces the insecure unsalted MD5 hash with bcrypt, a purpose-built password hashing algorithm that automatically generates and incorporates a unique salt and uses a configurable work factor (rounds=12) to make brute-force attacks computationally expensive. Additionally, the credential hash is no longer returned in the response to avoid unnecessary exposure of sensitive data.

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

def provision_iot_device_credentials(device_mac, admin_pin):
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    device_table = dynamodb.Table('IoTDeviceRegistry')
    salt = bcrypt.gensalt(rounds=12)
    hashed_pin = bcrypt.hashpw(admin_pin.encode(), salt).decode('utf-8')
    device_table.put_item(
        Item={
            'device_id': device_mac,
            'admin_credential': hashed_pin,
            'provisioned_at': str(datetime.now()),
            'status': 'active'
        }
    )
    return {'device_id': device_mac, 'status': 'provisioned'}
```
