Настройка DKIM + SPF + PHPMailer

2015-03-17
5.0 / 5 (8 votes)

Вы знаете что такое DKIM1 и SPF2, а также для чего собственно они нужны? Оказывается это очень полезная вещь, если вы отправляете почту со своего сервера и не хотите, что бы она попала в спам. И ниже я постараюсь подробно описать как его подключить к вашим письмам

Ниже описана последовательность действий для отправки почты со своего сервера с помощью PHPMailer + DKIM

Последовательность действий:

  • Заходим на dkimcore.org и формируем ключ для домена
  • Сохраняем их себе на компьютер или добавляем урл в избранное
  • Заходим в настройки домена и добавляем DKIM TXT запись к домену
  • Заходим снова в настройки домена и добавляем SPF TXT запись к домену
  • Скачиваем последнюю версию PHPMailer
  • Вносим изменения в PHPmailer и отправляем письма уже с DKIM подписью

Редактируем файл class.phpmailer.php

К сожалению в последней версии он не совсем корректно формирует подпись, поэтому пришлось внести некоторые правки

	public function preSend()
	{
		try {
			$this->DKIM_domain = "google.com";
			$this->DKIM_selector = "1426607473.google";
			$this->DKIM_private = JPATH_SITE . DS . 'libraries' . DS . 'phpmailer' . DS . 'dkim_private.key';
			$this->mailHeader = '';
			if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
				throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
			}
			// Set whether the message is multipart/alternative
			if (!empty($this->AltBody)) {
				$this->ContentType = 'multipart/alternative';
			}
			$this->error_count = 0; // reset errors
			$this->setMessageType();
			// Refuse to send an empty message unless we are specifically allowing it
			if (!$this->AllowEmpty and empty($this->Body)) {
				throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
			}
			$this->MIMEHeader = $this->createHeader();
			$this->MIMEBody = $this->createBody();
			// To capture the complete message when using mail(), create
			// an extra header list which createHeader() doesn't fold in
			if ($this->Mailer == 'mail') {
				if (count($this->to) > 0) {
					$this->mailHeader .= $this->addrAppend('To', $this->to);
				} else {
					$this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
				}
				$this->mailHeader .= $this->headerLine(
					'Subject',
					$this->encodeHeader($this->secureHeader(trim($this->Subject)))
				);
			}
			// digitally sign with DKIM if enabled
			if (!empty($this->DKIM_domain) && !empty($this->DKIM_private) && !empty($this->DKIM_selector)) {
				$header_dkim = $this->ACY_DKIM_Add($this->MIMEBody);
				$this->MIMEHeader = str_replace("\r\n", "\n", $header_dkim) . $this->MIMEHeader;
			}

			if (!empty($this->DKIM_domain)
				&& !empty($this->DKIM_private)
				&& !empty($this->DKIM_selector)
				&& file_exists($this->DKIM_private)) {
				/*
					$header_dkim = $this->DKIM_Add(
						$this->MIMEHeader . $this->mailHeader,
						$this->encodeHeader($this->secureHeader($this->Subject)),
						$this->MIMEBody
					);

					$this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
						str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
				*/
				$this->DKIM_private = file_get_contents($this->DKIM_private);
				$header_dkim = $this->ACY_DKIM_Add($this->MIMEBody);
				$this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
			}

			return true;
		} catch (phpmailerException $exc) {
			$this->setError($exc->getMessage());
			if ($this->exceptions) {
				throw $exc;
			}
			return false;
		}
	}

А вот так выглядит dkim_private.key:

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDDG9axtmNQ18
......
XCWmtRywbPmnqH2RNBTQSf
-----END RSA PRIVATE KEY-----

Добавляете две функции ACY_DKIM_Add и ACY_DKIM_Sign ничего не меняя в них

protected function ACY_DKIM_Add($body) {
		$DKIMsignatureType    = 'rsa-sha1'; // Signature & hash algorithms
		$DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
		$DKIMquery            = 'dns/txt'; // Query method
		$DKIMtime             = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)

		$subject = $this->EncodeHeader($this->SecureHeader($this->Subject));

		$subjecta_header       = "Subject: $subject";
		$from = array();
		$from[0][0] = trim($this->From);
		$from[0][1] = $this->FromName;
		$fromc_header = $this->AddrAppend('From', $from);
		$toy_header = $this->AddrAppend('To', $this->to);

		$body     = $this->DKIM_BodyC($body);
		$DKIMlen  = strlen($body) ; // Length of body
		$DKIMb64  = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body
		$ident    = (empty($this->DKIM_identity))? '' : " i=" . $this->DKIM_identity . ";";
		$dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n".
			"\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . "; h=from:to:subject;\r\n".
			"\td=" . $this->DKIM_domain . ";" . $ident ." bh=" . $DKIMb64 . ";\r\n".
			"\tb=";
		$toSign   = $this->DKIM_HeaderC($fromc_header . "\r\n" . $toy_header . "\r\n" . $subjecta_header . "\r\n" . $dkimhdrs);
		$signed   = wordwrap($this->ACY_DKIM_Sign($toSign),60,"\r\n\t",true);
		if(empty($signed)) return '';
		return $dkimhdrs.$signed."\r\n";
	}
protected function ACY_DKIM_Sign($s) {
		if (!empty($this->DKIM_passphrase)) {
			$privKey = openssl_pkey_get_private($this->DKIM_private,$this->DKIM_passphrase);
		} else {
			$privKey = $this->DKIM_private;
		}
		$signature = '';
		if (openssl_sign($s, $signature, $privKey)) {
			return base64_encode($signature);
		}
	}

Пример DKIM И SPF записей в домене:

Имя записиТипКонтентTTLПриоритет
1426607473.
google._domainkey.google.com
TXT v=DKIM1;t=s;p=MIGfMA0GC....AQAB 3600  
google.com TXT v=spf1 a mx ip4:91.160.1.10 ~all 3600  

v=spf1 a mx ip4:91.160.1.10 ~all - Тут только указываете свой ip smtp сервера

Все , теперь должно все работать. Ниже можете посмотреть принскрины, а также скачать полностью архив для более детального изучения.

2015-04-29 Важный апдейт, текст сообщения обязательно оборачиваейте функцией wordwrap, иначе если у вас длинный текст, то DKIM подпись будет не верна. Пример -> wordwrap($message) 

  • DKIM1 - метод E-mail аутентификации (DomainKeys Identified Mail)
  • SPF2 - расширение для протокола отправки электронной почты через SMTP. SPF определен в RFC 7208.Благодаря SPF можно проверить, не подделан ли домен отправителя. (Sender Policy Framework)
Read 5379 times Last modified on 2019-07-21

3 comments

  • Константин
    Константин 2021-12-17

    Ваша статья помогла понять, что нужно писать в DKIM сигнатуре, до этого нигде не мог найти этого описания и какой значение пишется в DKIM сигнатуре. Благодарю!

  • Евгений
    Евгений 2016-04-25

    Вопрос
    Вы это (1426607473.google) откуда взяли?
    И еще - dkim_private.key - это такой длинный непонятный текст (v=DKIM1; k=rsa; p=MIGf....)?

    После того как сформируете сертификат на http://dkimcore.org/ увидите цифры.ваш_домен вот его и берете. Пример http://dkimcore.org/tools/key/1461781159-8becc6c70f61ba75e6037ec8498bfa9f/ 
    По второму вопросу, это код начинающийся с -----BEGIN RSA KEY и заканчивающийся -----END RSA PRIVATE KEY (В том чесле эта надписи) Пример:
    $this->DKIM_private = JPATH_SITE . DS . 'libraries' . DS . 'phpmailer' . DS . 'dkim_private.key';
    А в самом файле уже код:
    -----BEGIN RSA PRIVATE KEY-----
    MIICXAIB.............
    -----END RSA PRIVATE KEY-----

  • сaptainpower
    сaptainpower 2016-02-21

    2 два дня бился. Сделал по статье - ВСЕ ПОЛУЧИЛОСЬ УРА!!!

    Очень рад, что кому-то еще пригодилась данная статья

Leave a comment

Make sure you enter the (*) required information where indicated. HTML code is not allowed.