PHPOffice/PHPWord · error · Exception

Failed to convert password to UCS-2LE

Error message

Failed to convert password to UCS-2LE

What it means

PasswordEncoder::hashPassword() converts the UTF-8 password to UCS-2LE, a required step of the Word document-protection hashing algorithm. mb_convert_encoding() returned a non-string (per the explicit is_string() check), so the hash cannot be computed and Exception is thrown.

Solutions

  1. Install/enable the mbstring extension (php-mbstring package) and restart the web server/CLI.
  2. Verify mb_convert_encoding($p, 'UCS-2LE', 'UTF-8') works in your environment with a test script.
  3. Ensure the password is valid UTF-8 before calling setDocumentProtection (run mb_check_encoding or Text::toUTF8).
  4. If mbstring cannot be added, pre-encode with iconv('UTF-8', 'UCS-2LE', $password) as a workaround in custom code.

Example fix

// before
$protection->setDocumentProtection($passwordWithBadEncoding);
// after
if (!mb_check_encoding($password, 'UTF-8')) {
    $password = mb_convert_encoding($password, 'UTF-8', 'ISO-8859-1');
}
$protection->setDocumentProtection($password);
Defensive patterns

Strategy: try-catch

Validate before calling

if (!function_exists('mb_convert_encoding')) {
    throw new RuntimeException('ext-mbstring required for document protection');
}
if (!mb_check_encoding($password, 'UTF-8')) {
    $password = mb_convert_encoding($password, 'UTF-8', 'ISO-8859-1');
}

Type guard

function isConvertibleToUcs2(string $password): bool
{
    return function_exists('mb_convert_encoding')
        && is_string(mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'));
}

Try / catch

try {
    $settings->setDocumentProtection($password);
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
    if (str_contains($e->getMessage(), 'UCS-2LE')) {
        throw new RuntimeException('mbstring/UCS-2LE conversion unavailable', 0, $e);
    }
    throw $e;
}

Prevention

When it happens

Trigger: hashPassword() invoked (directly or via setDocumentProtection) when mb_convert_encoding($password, 'UCS-2LE', 'UTF-8') fails — typically the mbstring extension is absent or misconfigured, or the encoding name is not supported by the installed libmbfl build.

Common situations: Deploying to a minimal PHP environment without ext-mbstring (composer may install a polyfill that behaves differently); unusual PHP builds lacking UCS-2LE; passwords containing malformed UTF-8 from external sources.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of PHPOffice/PHPWord@aef95c0415 (2026-09-14). Data as JSON: /api/errors/eb26b2dc3ee2e14a. Report an issue: GitHub.

Appendix: source

Thrown at src/PhpWord/Shared/Microsoft/PasswordEncoder.php:125

     * @param string $password
     * @param string $algorithmName
     * @param string $salt
     * @param int $spinCount
     *
     * @return string
     */
    public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000)
    {
        $origEncoding = mb_internal_encoding();
        mb_internal_encoding('UTF-8');

        $password = mb_substr($password, 0, min(self::$passwordMaxLength, mb_strlen($password)));

        //   Get the single-byte values by iterating through the Unicode characters of the truncated password.
        //   For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte.
        $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
        if (!is_string($passUtf8)) {
            throw new Exception('Failed to convert password to UCS-2LE');
        }

        $byteChars = [];
        for ($i = 0; $i < mb_strlen($password); ++$i) {
            $byteChars[$i] = ord(substr($passUtf8, $i * 2, 1));

            if ($byteChars[$i] == 0) {
                $byteChars[$i] = ord(substr($passUtf8, $i * 2 + 1, 1));
            }
        }

        // build low-order word and hig-order word and combine them
        $combinedKey = self::buildCombinedKey($byteChars);
        // build reversed hexadecimal string
        $hex = str_pad(strtoupper(dechex($combinedKey & self::ALL_ONE_BITS)), 8, '0', \STR_PAD_LEFT);
        $reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1];

        $generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8');

View on GitHub (pinned to aef95c0415)