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
- Install/enable the mbstring extension (php-mbstring package) and restart the web server/CLI.
- Verify mb_convert_encoding($p, 'UCS-2LE', 'UTF-8') works in your environment with a test script.
- Ensure the password is valid UTF-8 before calling setDocumentProtection (run mb_check_encoding or Text::toUTF8).
- 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
- Require ext-mbstring in composer.json and verify it in deployment health checks
- Validate passwords are valid UTF-8 before applying document protection
- Test password protection on the exact PHP build used in production
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
- Unable to convert text to UTF-8
- Invalid value, on of ' . implode(', ', $position) . '…
- Invalid value, on of ' . implode(', ', $restartNumbers) . '…
- Invalid value, dirty or clean possible
- Invalid value, alignments of ' . implode(', '…
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)